mirror of
				https://gitlab.com/comunic/comunicapiv3
				synced 2025-10-30 23:24:42 +00:00 
			
		
		
		
	Continue refactoring
This commit is contained in:
		| @@ -53,7 +53,7 @@ impl AccountExportAPI { | ||||
|             conversations_list: ConversationAPI::for_list(&export.conversations), | ||||
|             conversations_messages: export.conversation_messages | ||||
|                 .iter() | ||||
|                 .map(|r| (r.0.clone(), ConversationMessageAPI::for_list(r.1))) | ||||
|                 .map(|r| (r.0.id(), ConversationMessageAPI::for_list(r.1))) | ||||
|                 .collect(), | ||||
|             friends_list: FriendAPI::from_list(&export.friends_list), | ||||
|             groups: export.groups | ||||
|   | ||||
| @@ -17,7 +17,7 @@ pub struct CallPeerInterruptedStreamingAPI { | ||||
| impl CallPeerInterruptedStreamingAPI { | ||||
|     pub fn new(call_id: &ConvID, peer_id: &UserID) -> Self { | ||||
|         Self { | ||||
|             callID: call_id.clone(), | ||||
|             callID: call_id.id(), | ||||
|             peerID: peer_id.id(), | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ pub struct CallPeerReadyAPI { | ||||
| impl CallPeerReadyAPI { | ||||
|     pub fn new(call_id: &ConvID, user_id: &UserID) -> Self { | ||||
|         Self { | ||||
|             callID: call_id.clone(), | ||||
|             callID: call_id.id(), | ||||
|             peerID: user_id.id(), | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,54 +1,58 @@ | ||||
| //! # Conversation API object | ||||
| //! | ||||
| //! @author Pierre Hubert | ||||
| use serde::{Serialize, Serializer}; | ||||
| use serde::Serialize; | ||||
|  | ||||
| use crate::api_data::legacy_api_bool::LegacyBool; | ||||
| use crate::controllers::calls_controller; | ||||
| use crate::data::conversation::Conversation; | ||||
| use crate::data::conversation::{Conversation, ConversationMember}; | ||||
| use crate::helpers::calls_helper; | ||||
|  | ||||
| /// Special implementation of conversation name (false if none / the name otherwise) | ||||
| struct ConvName(Option<String>); | ||||
|  | ||||
| impl Serialize for ConvName { | ||||
|     fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where | ||||
|         S: Serializer { | ||||
|         match &self.0 { | ||||
|             None => serializer.serialize_bool(false), | ||||
|             Some(n) => serializer.serialize_str(n) | ||||
|         } | ||||
|     } | ||||
| #[derive(Serialize)] | ||||
| struct ConversationMembersAPI { | ||||
|     user_id: u64, | ||||
|     last_message_seen: u64, | ||||
|     following: bool, | ||||
|     is_admin: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| #[allow(non_snake_case)] | ||||
| pub struct ConversationAPI { | ||||
|     ID: u64, | ||||
|     ID_owner: u64, | ||||
|     last_active: u64, | ||||
|     name: ConvName, | ||||
|     following: LegacyBool, | ||||
|     saw_last_message: LegacyBool, | ||||
|     members: Vec<u64>, | ||||
|     canEveryoneAddMembers: bool, | ||||
|     id: u64, | ||||
|     last_activity: u64, | ||||
|     name: Option<String>, | ||||
|     color: Option<String>, | ||||
|     background: Option<String>, | ||||
|     group_id: Option<u64>, | ||||
|     members: Vec<ConversationMembersAPI>, | ||||
|     can_everyone_add_members: bool, | ||||
|     can_have_call: bool, | ||||
|     can_have_video_call: bool, | ||||
|     has_call_now: bool, | ||||
| } | ||||
|  | ||||
| impl ConversationMembersAPI { | ||||
|     pub fn new(m: &ConversationMember) -> Self { | ||||
|         Self { | ||||
|             user_id: m.user_id.id(), | ||||
|             last_message_seen: m.last_message_seen, | ||||
|             following: m.following, | ||||
|             is_admin: m.is_admin, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConversationAPI { | ||||
|     /// Construct a new Conversation instance | ||||
|     pub fn new(conv: &Conversation) -> ConversationAPI { | ||||
|         ConversationAPI { | ||||
|             ID: conv.id, | ||||
|             ID_owner: conv.owner_id.id(), | ||||
|             last_active: conv.last_active, | ||||
|             name: ConvName(conv.name.clone()), | ||||
|             following: LegacyBool(conv.following), | ||||
|             saw_last_message: LegacyBool(conv.saw_last_message), | ||||
|             members: conv.members.iter().map(|x| x.id()).collect(), | ||||
|             canEveryoneAddMembers: conv.can_everyone_add_members, | ||||
|             id: conv.id.id(), | ||||
|             last_activity: conv.last_activity, | ||||
|             name: conv.name.clone(), | ||||
|             members: conv.members.iter().map(ConversationMembersAPI::new).collect(), | ||||
|             can_everyone_add_members: conv.can_everyone_add_members, | ||||
|             color: conv.color.clone(), | ||||
|             background: conv.background.clone(), | ||||
|             group_id: conv.group_id.as_ref().map(|i| i.id()), | ||||
|  | ||||
|             can_have_call: calls_helper::can_have_call(conv), | ||||
|             can_have_video_call: calls_helper::can_have_video_calls(conv), | ||||
|   | ||||
| @@ -79,7 +79,7 @@ impl ConversationMessageAPI { | ||||
|  | ||||
|         ConversationMessageAPI { | ||||
|             id: msg.id, | ||||
|             conv_id: msg.conv_id, | ||||
|             conv_id: msg.conv_id.id(), | ||||
|             user_id: msg.user_id.clone().map(|u| u.id()), | ||||
|             time_insert: msg.time_sent, | ||||
|             message: msg.message.clone(), | ||||
|   | ||||
| @@ -1,37 +0,0 @@ | ||||
| //! # Conversation refresh result | ||||
| //! | ||||
| //! Note : this structure is now deprecated and should no longer be used for further developments | ||||
| //! | ||||
| //! @author Pierre Hubert | ||||
|  | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use serde::Serialize; | ||||
|  | ||||
| use crate::api_data::conversation_message_api::ConversationMessageAPI; | ||||
| use crate::data::conversation_message::ConversationMessage; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| #[allow(non_snake_case)] | ||||
| pub struct ConversationRefreshResultAPI { | ||||
|     #[serde(flatten)] | ||||
|     list: HashMap<String, Vec<ConversationMessageAPI>>, | ||||
| } | ||||
|  | ||||
| impl ConversationRefreshResultAPI { | ||||
|     /// Create a new list | ||||
|     pub fn new(list: HashMap<u64, Vec<ConversationMessage>>) -> ConversationRefreshResultAPI { | ||||
|         let list = list | ||||
|             .iter() | ||||
|             .map(|v| ( | ||||
|                 format!("conversation-{}", v.0), | ||||
|                 ConversationMessageAPI::for_list(v.1) | ||||
|             )) | ||||
|             .collect(); | ||||
|  | ||||
|         ConversationRefreshResultAPI { | ||||
|             list | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -18,7 +18,7 @@ pub struct JoinedCallMessage { | ||||
| impl JoinedCallMessage { | ||||
|     pub fn new(call_id: &ConvID, user_id: &UserID) -> Self { | ||||
|         Self { | ||||
|             callID: call_id.clone(), | ||||
|             callID: call_id.id(), | ||||
|             userID: user_id.id(), | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -18,7 +18,7 @@ pub struct LeftCallMessage { | ||||
| impl LeftCallMessage { | ||||
|     pub fn new(call_id: &ConvID, user_id: &UserID) -> Self { | ||||
|         Self { | ||||
|             callID: call_id.clone(), | ||||
|             callID: call_id.id(), | ||||
|             userID: user_id.id(), | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -21,7 +21,6 @@ pub mod conversation_api; | ||||
| mod legacy_api_bool; | ||||
| pub mod res_find_private_conversations; | ||||
| pub mod conversation_message_api; | ||||
| pub mod conversations_refresh_api; | ||||
| pub mod res_count_unread_conversations; | ||||
| pub mod list_unread_conversations_api; | ||||
| pub mod global_search_result_api; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ pub struct NewCallSignalAPI { | ||||
| impl NewCallSignalAPI { | ||||
|     pub fn new(call_id: &ConvID, peer_id: &UserID, data: &str) -> Res<Self> { | ||||
|         Ok(Self { | ||||
|             callID: call_id.clone(), | ||||
|             callID: call_id.id(), | ||||
|             peerID: peer_id.id(), | ||||
|             data: serde_json::from_str(data)?, | ||||
|         }) | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| //! @author Pierre Hubert | ||||
|  | ||||
| use serde::{Serialize}; | ||||
| use crate::data::conversation::ConvID; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| #[allow(non_snake_case)] | ||||
| @@ -13,9 +14,9 @@ pub struct ResCreateConversation { | ||||
|  | ||||
| impl ResCreateConversation { | ||||
|     /// Construct a new Result instance | ||||
|     pub fn new(conv_id: u64) -> ResCreateConversation { | ||||
|     pub fn new(conv_id: ConvID) -> ResCreateConversation { | ||||
|         ResCreateConversation { | ||||
|             conversationID: conv_id, | ||||
|             conversationID: conv_id.id(), | ||||
|             success: "The conversation was successfully created!".to_string(), | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -4,6 +4,8 @@ | ||||
|  | ||||
| use serde::Serialize; | ||||
|  | ||||
| use crate::data::conversation::ConvID; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| #[allow(non_snake_case)] | ||||
| pub struct ResFindPrivateConversations { | ||||
| @@ -12,9 +14,9 @@ pub struct ResFindPrivateConversations { | ||||
|  | ||||
| impl ResFindPrivateConversations { | ||||
|     /// Construct a new instance of this structure | ||||
|     pub fn new(list: Vec<u64>) -> ResFindPrivateConversations { | ||||
|     pub fn new(list: Vec<ConvID>) -> ResFindPrivateConversations { | ||||
|         ResFindPrivateConversations { | ||||
|             conversationsID: list | ||||
|             conversationsID: list.iter().map(|i| i.id()).collect() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -36,7 +36,6 @@ pub mod database_tables_names { | ||||
|     pub const CONV_LIST_TABLE: &str = "comunic_conversations_list"; | ||||
|     pub const CONV_MEMBERS_TABLE: &str = "comunic_conversations_members"; | ||||
|     pub const CONV_MESSAGES_TABLE: &str = "comunic_conversations_messages"; | ||||
|     pub const CONV_INFO_VIEW: &str = "comunic_conversations_info"; | ||||
|  | ||||
|     /// Posts table | ||||
|     pub const POSTS_TABLE: &str = "texte"; | ||||
| @@ -163,4 +162,7 @@ pub const INITIAL_REFRESH_LOAD_INTERVAL: Duration = Duration::from_secs(5); | ||||
| pub const CLEAN_UP_INTERVAL: Duration = Duration::from_secs(60 * 60); | ||||
|  | ||||
| /// Minimal mobile version supported | ||||
| pub const MIN_SUPPORTED_MOBILE_VERSION: &str = "1.1.1"; | ||||
| pub const MIN_SUPPORTED_MOBILE_VERSION: &str = "1.1.1"; | ||||
|  | ||||
| /// Minimum message length | ||||
| pub const MIN_CONVERSATION_MESSAGE_LENGTH: usize = 1; | ||||
| @@ -13,7 +13,6 @@ use crate::api_data::joined_call_message::JoinedCallMessage; | ||||
| use crate::api_data::left_call_message::LeftCallMessage; | ||||
| use crate::api_data::new_call_signal::NewCallSignalAPI; | ||||
| use crate::api_data::user_calls_config::UserCallsConfig; | ||||
| use crate::routes::RequestResult; | ||||
| use crate::controllers::user_ws_controller; | ||||
| use crate::data::base_request_handler::BaseRequestHandler; | ||||
| use crate::data::call_signal::{CallSignal, CloseCallStream, IceCandidate, NewUserCallSignal, SdpType, UserCallOfferRequest}; | ||||
| @@ -27,11 +26,12 @@ use crate::data::user_ws_message::UserWsMessage; | ||||
| use crate::data::user_ws_request_handler::UserWsRequestHandler; | ||||
| use crate::helpers::{calls_helper, conversations_helper, events_helper}; | ||||
| use crate::helpers::events_helper::Event; | ||||
| use crate::routes::RequestResult; | ||||
|  | ||||
| impl UserWsRequestHandler { | ||||
|     /// Get the ID of a call included in a WebSocket request | ||||
|     fn post_call_id(&mut self, name: &str) -> Res<ConvID> { | ||||
|         let conv_id = self.post_u64(name)?; | ||||
|         let conv_id = ConvID::new(self.post_u64(name)?); | ||||
|  | ||||
|         if !self.get_conn().is_having_call_with_conversation(&conv_id) { | ||||
|             self.forbidden("You do not belong to this call!".to_string())?; | ||||
| @@ -125,7 +125,7 @@ pub fn is_conversation_having_call(conv_id: &ConvID) -> bool { | ||||
|     }); | ||||
|  | ||||
|     if let Err(e) = res { | ||||
|         eprintln!("Failed to check if a conversation is having call! Conversation: {} / Error: {:#?}", conv_id, e); | ||||
|         eprintln!("Failed to check if a conversation is having call! Conversation: {} / Error: {:#?}", conv_id.id(), e); | ||||
|     } | ||||
|  | ||||
|     found | ||||
| @@ -133,10 +133,10 @@ pub fn is_conversation_having_call(conv_id: &ConvID) -> bool { | ||||
|  | ||||
| /// Join a call | ||||
| pub fn join_call(r: &mut UserWsRequestHandler) -> RequestResult { | ||||
|     let conv_id = r.post_conv_id("convID")?; | ||||
|     let conv_id = r.post_conv("convID")?.conv_id; | ||||
|  | ||||
|     // Check if the conversation can have a call | ||||
|     let conv = conversations_helper::get_single(conv_id, r.user_id_ref()?)?; | ||||
|     let conv = conversations_helper::get_single(conv_id)?; | ||||
|     if !calls_helper::can_have_call(&conv) { | ||||
|         r.forbidden("This conversation can not be used to make calls!".to_string())?; | ||||
|     } | ||||
| @@ -177,7 +177,7 @@ pub fn leave_call(r: &mut UserWsRequestHandler) -> RequestResult { | ||||
|     // Warning ! For some technical reasons, we do not check if the user | ||||
|     // really belongs to the conversation, so be careful when manipulating | ||||
|     // conversation ID here | ||||
|     let conv_id = r.post_u64("convID")?; | ||||
|     let conv_id = ConvID::new(r.post_u64("convID")?); | ||||
|  | ||||
|     // Check if the user was not in the conversation | ||||
|     if !r.get_conn().is_having_call_with_conversation(&conv_id) { | ||||
| @@ -208,7 +208,7 @@ pub fn get_members_list(r: &mut UserWsRequestHandler) -> RequestResult { | ||||
|  | ||||
| /// Get the hash associated to a call | ||||
| pub fn gen_call_hash(call_id: &ConvID, peer_id: &UserID) -> String { | ||||
|     format!("{}-{}", call_id, peer_id.id()) | ||||
|     format!("{}-{}", call_id.id(), peer_id.id()) | ||||
| } | ||||
|  | ||||
| /// Handles client signal | ||||
| @@ -357,7 +357,7 @@ pub fn make_user_leave_call(conv_id: &ConvID, connection: &UserWsConnection) -> | ||||
|  | ||||
|     // Notify user (if possible) | ||||
|     if connection.session.connected() { | ||||
|         user_ws_controller::send_to_client(connection, &UserWsMessage::no_id_message("call_closed", conv_id)?)?; | ||||
|         user_ws_controller::send_to_client(connection, &UserWsMessage::no_id_message("call_closed", conv_id.id())?)?; | ||||
|     } | ||||
|  | ||||
|     // Close main stream (sender) | ||||
| @@ -418,7 +418,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res { | ||||
|                 return Ok(()); | ||||
|             } | ||||
|  | ||||
|             let call_id = split[0].parse::<u64>()?; | ||||
|             let call_id = ConvID::new(split[0].parse::<u64>()?); | ||||
|             let peer_id = UserID::new(split[1].parse::<u64>()?); | ||||
|  | ||||
|             let target_user = UserID::new(msg.peer_id.parse::<u64>()?); | ||||
|   | ||||
| @@ -2,18 +2,18 @@ | ||||
| //! | ||||
| //! @author Pierre Hubert | ||||
|  | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use crate::api_data::conversation_api::ConversationAPI; | ||||
| use crate::api_data::conversation_message_api::ConversationMessageAPI; | ||||
| use crate::api_data::conversations_refresh_api::ConversationRefreshResultAPI; | ||||
| use crate::api_data::list_unread_conversations_api::UnreadConversationAPI; | ||||
| use crate::api_data::res_count_unread_conversations::ResultCountUnreadConversations; | ||||
| use crate::api_data::res_create_conversation::ResCreateConversation; | ||||
| use crate::api_data::res_find_private_conversations::ResFindPrivateConversations; | ||||
| use crate::routes::RequestResult; | ||||
| use crate::constants::MIN_CONVERSATION_MESSAGE_LENGTH; | ||||
| use crate::controllers::user_ws_controller; | ||||
| use crate::data::base_request_handler::BaseRequestHandler; | ||||
| use crate::data::conversation::{ConversationMemberSetting, NewConversationSettings}; | ||||
| use crate::data::conversation_message::ConversationMessageFile; | ||||
| use crate::data::error::Res; | ||||
| use crate::data::http_request_handler::HttpRequestHandler; | ||||
| use crate::data::new_conversation::NewConversation; | ||||
| @@ -23,6 +23,7 @@ use crate::data::user_ws_connection::UserWsConnection; | ||||
| use crate::data::user_ws_message::UserWsMessage; | ||||
| use crate::helpers::{conversations_helper, events_helper, user_helper}; | ||||
| use crate::helpers::events_helper::Event; | ||||
| use crate::routes::RequestResult; | ||||
| use crate::utils::string_utils::remove_html_nodes; | ||||
|  | ||||
| /// Create a new conversation | ||||
| @@ -58,6 +59,9 @@ pub fn create(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|         owner_following: r.post_bool("follow")?, | ||||
|         members, | ||||
|         can_everyone_add_members: r.post_bool_opt("canEveryoneAddMembers", true), | ||||
|         color: r.post_color_opt("color")?, | ||||
|         group_id: None, | ||||
|         background: None,// TODO : add support for background | ||||
|     }; | ||||
|  | ||||
|     // Create the conversation | ||||
| @@ -74,77 +78,94 @@ pub fn get_list(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|  | ||||
| /// Get information about a single conversation | ||||
| pub fn get_single(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let conversation_id = r.post_conv_id("conversationID")?; | ||||
|     let conv = conversations_helper::get_single(conversation_id, &r.user_id()?)?; | ||||
|     let conv = r.post_conv("conversationID")?; | ||||
|     let conv = conversations_helper::get_single(conv.conv_id)?; | ||||
|  | ||||
|     r.set_response(ConversationAPI::new(&conv)) | ||||
| } | ||||
|  | ||||
| /// Update the settings of a conversation | ||||
| pub fn update_settings(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let conv_id = r.post_conv_id("conversationID")?; | ||||
|     let is_moderator = conversations_helper::is_user_moderator(&r.user_id()?, conv_id)?; | ||||
|     let conv_membership = r.post_conv("conversationID")?; | ||||
|     let conv = conversations_helper::get_single(conv_membership.conv_id)?; | ||||
|  | ||||
|     // Update following state, if required | ||||
|     if r.has_post_parameter("following") { | ||||
|         conversations_helper::set_following( | ||||
|             &r.user_id()?, | ||||
|             conv_id, | ||||
|             conv_membership.member_id, | ||||
|             r.post_bool("following")?, | ||||
|         )?; | ||||
|     } | ||||
|  | ||||
|     // Update members list | ||||
|     if r.has_post_parameter("members") { | ||||
|         let mut members = r.post_numbers_list("members", 1)? | ||||
|             .iter() | ||||
|             .map(|f| UserID::new(f.clone())) | ||||
|             .collect::<Vec<UserID>>(); | ||||
|     if r.has_post_parameter("members") && !conv.is_managed() { | ||||
|         let mut members = r.post_users_id("members")? | ||||
|             .into_iter() | ||||
|             .map(|user_id| ConversationMemberSetting { user_id, set_admin: false }) | ||||
|             .collect::<Vec<ConversationMemberSetting>>(); | ||||
|         let admins = r.post_users_id("admins")?; | ||||
|  | ||||
|         let can_everyone_add_members = conversations_helper::can_everyone_add_members(conv_id)?; | ||||
|         let can_everyone_add_members = conversations_helper::can_everyone_add_members(conv_membership.conv_id)?; | ||||
|  | ||||
|         if !is_moderator && !can_everyone_add_members { | ||||
|         if !conv_membership.is_admin && !can_everyone_add_members { | ||||
|             r.forbidden("You can not update the list of members of this conversation!".to_string())?; | ||||
|         } | ||||
|  | ||||
|         // Check if the members of the conversation really exists | ||||
|         for member in &members { | ||||
|             if !user_helper::exists(member)? { | ||||
|                 r.not_found(format!("User {} not found!", member.id()))?; | ||||
|             } | ||||
|         // Set same admin status as earlier | ||||
|         if !conv_membership.is_admin { | ||||
|             members = members | ||||
|                 .into_iter() | ||||
|                 .map(|mut n| { | ||||
|                     n.set_admin = conv.is_admin(&n.user_id); | ||||
|                     n | ||||
|                 }) | ||||
|                 .collect() | ||||
|         } | ||||
|  | ||||
|         if !members.contains(&r.user_id()?) { | ||||
|             members.push(r.user_id()?); | ||||
|         // If the user is an admin, use the values he gave | ||||
|         else { | ||||
|             members = members | ||||
|                 .into_iter() | ||||
|                 .map(|mut n| { | ||||
|                     n.set_admin = admins.contains(&n.user_id); | ||||
|                     n | ||||
|                 }) | ||||
|                 .collect() | ||||
|         } | ||||
|  | ||||
|         conversations_helper::set_members(conv_id, &members, is_moderator)?; | ||||
|         // Current user can not touch his own membership, so revert it back forcefully | ||||
|         members = members | ||||
|             .into_iter() | ||||
|             .filter(|m| !m.user_id.eq(&conv_membership.user_id)) | ||||
|             .collect::<Vec<ConversationMemberSetting>>(); | ||||
|  | ||||
|         members.push(ConversationMemberSetting { | ||||
|             user_id: r.user_id()?, | ||||
|             set_admin: conv_membership.is_admin, | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         conversations_helper::set_members(conv_membership.conv_id, &members, conv_membership.is_admin)?; | ||||
|     } | ||||
|  | ||||
|     // Change moderator settings | ||||
|     if r.has_post_parameter("name") || r.has_post_parameter("canEveryoneAddMembers") { | ||||
|         if !is_moderator { | ||||
|     if r.has_post_parameter("name") | ||||
|         && r.has_post_parameter("canEveryoneAddMembers") | ||||
|         && r.has_post_parameter("color") { | ||||
|         if !conv_membership.is_admin { | ||||
|             r.forbidden("You are not allowed to perform changes on this conversation!".to_string())?; | ||||
|         } | ||||
|  | ||||
|         // Change conversation name | ||||
|         if r.has_post_parameter("name") { | ||||
|             let name = r.post_string_opt("name", 0, true)?; | ||||
|             let name = if name.eq("false") || name.is_empty() { | ||||
|                 None | ||||
|             } else { | ||||
|                 Some(remove_html_nodes(&name)) | ||||
|             }; | ||||
|  | ||||
|             conversations_helper::set_name(conv_id, name)?; | ||||
|         } | ||||
|         let new_settings = NewConversationSettings { | ||||
|             conv_id: conv_membership.conv_id, | ||||
|             name: r.post_string_optional("name").map(|s| remove_html_nodes(&s)), | ||||
|             color: r.post_color_opt("color")?, | ||||
|             can_everyone_add_members: r.post_bool("canEveryoneAddMembers")?, | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         // Change "canEveryoneAddMembers" parameter | ||||
|         if r.has_post_parameter("canEveryoneAddMembers") { | ||||
|             conversations_helper::set_can_everyone_add_members( | ||||
|                 conv_id, r.post_bool("canEveryoneAddMembers")?)?; | ||||
|         } | ||||
|         conversations_helper::set_settings(new_settings)?; | ||||
|     } | ||||
|  | ||||
|     r.success("Conversation information successfully updated!") | ||||
| @@ -169,6 +190,9 @@ pub fn find_private(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|             owner_following: true, | ||||
|             members: vec![r.user_id()?, other_user], | ||||
|             can_everyone_add_members: true, | ||||
|             color: r.post_color_opt("color")?, | ||||
|             background: None, | ||||
|             group_id: None, | ||||
|         }; | ||||
|         let conv_id = conversations_helper::create(&new_conv)?; | ||||
|         list.push(conv_id); | ||||
| @@ -177,80 +201,25 @@ pub fn find_private(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     r.set_response(ResFindPrivateConversations::new(list)) | ||||
| } | ||||
|  | ||||
| /// DEPRECATED : refresh current user conversations | ||||
| /// | ||||
| /// This method was used only by ComunicWeb before the introduction of WebSockets | ||||
| pub fn refresh_list(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let mut list = HashMap::new(); | ||||
|  | ||||
|     // Check for new conversations | ||||
|     if r.has_post_parameter("newConversations") { | ||||
|         for conv_id in r.post_numbers_list("newConversations", 0)? { | ||||
|             if !conversations_helper::does_user_belongs_to(&r.user_id()?, conv_id)? { | ||||
|                 r.forbidden(format!("Your do not belongs to conversation {} !", conv_id))?; | ||||
|             } | ||||
|  | ||||
|             let list_conv = conversations_helper::get_last_messages(conv_id, 10)?; | ||||
|             list.insert(conv_id as u64, list_conv); | ||||
|  | ||||
|             conversations_helper::mark_user_seen(conv_id as u64, &r.user_id()?)?; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Check for refresh of already initialized conversations | ||||
|     if r.has_post_parameter("toRefresh") { | ||||
|         let v: HashMap<String, HashMap<String, serde_json::Value>> = | ||||
|             serde_json::from_str(r.post_string("toRefresh")?.as_str())?; | ||||
|  | ||||
|         for (k, v) in v { | ||||
|             // Get conversation ID | ||||
|             if !k.starts_with("conversation-") { | ||||
|                 return r.bad_request("Entries of 'toRefresh' must start with 'conversation-'!".to_string()); | ||||
|             } | ||||
|             let conv_id = k.replace("conversation-", "").parse::<u64>()?; | ||||
|  | ||||
|             // Extract last message id | ||||
|             if !v.contains_key("last_message_id") { | ||||
|                 return r.bad_request(format!("Missing 'last_message_id' in conversation {}!", conv_id)); | ||||
|             } | ||||
|             let last_message_id = v["last_message_id"].as_u64().unwrap_or(0); | ||||
|  | ||||
|             // Check user rights | ||||
|             if !conversations_helper::does_user_belongs_to(&r.user_id()?, conv_id)? { | ||||
|                 return r.forbidden(format!("You do not belong to conversation {}!", conv_id)); | ||||
|             } | ||||
|  | ||||
|             let list_conv = conversations_helper::get_new_messages(conv_id, last_message_id)?; | ||||
|             list.insert(conv_id, list_conv); | ||||
|  | ||||
|             conversations_helper::mark_user_seen(conv_id as u64, &r.user_id()?)?; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     r.set_response(ConversationRefreshResultAPI::new(list)) | ||||
| } | ||||
|  | ||||
| /// Refresh a single conversation | ||||
| pub fn refresh_single(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let conv_id = r.post_conv_id("conversationID")?; | ||||
|     let conv = r.post_conv("conversationID")?; | ||||
|     let last_message_id = r.post_u64("last_message_id")?; | ||||
|  | ||||
|     let messages = match last_message_id { | ||||
|         // Get latest messages of the conversation | ||||
|         0 => conversations_helper::get_last_messages(conv_id, 10)?, | ||||
|         0 => conversations_helper::get_last_messages(conv.member_id, 10)?, | ||||
|  | ||||
|         // Get new messages | ||||
|         _ => conversations_helper::get_new_messages(conv_id, last_message_id)?, | ||||
|         _ => conversations_helper::get_new_messages(conv.member_id, last_message_id)?, | ||||
|     }; | ||||
|  | ||||
|     conversations_helper::mark_user_seen(conv_id, &r.user_id()?)?; | ||||
|  | ||||
|     r.set_response(ConversationMessageAPI::for_list(&messages)) | ||||
| } | ||||
|  | ||||
| /// Get older messages of a conversation | ||||
| pub fn get_older_messages(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let conv_id = r.post_conv_id("conversationID")?; | ||||
|     let conv = r.post_conv("conversationID")?; | ||||
|     let max_id = r.post_u64("oldest_message_id")? - 1; | ||||
|  | ||||
|     // Determine limit | ||||
| @@ -261,31 +230,44 @@ pub fn get_older_messages(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|         _ => 60 | ||||
|     }; | ||||
|  | ||||
|     let messages = conversations_helper::get_older_messages(conv_id, max_id, limit)?; | ||||
|     let messages = conversations_helper::get_older_messages(conv.member_id, max_id, limit)?; | ||||
|  | ||||
|     r.set_response(ConversationMessageAPI::for_list(&messages)) | ||||
| } | ||||
|  | ||||
| /// Send a new message | ||||
| pub fn send_message(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let conv_id = r.post_conv_id("conversationID")?; | ||||
|     let message = r.post_string_without_html("message", 0, false)?; | ||||
|     let conv = r.post_conv("conversationID")?; | ||||
|  | ||||
|     // TODO : add support for other files type | ||||
|  | ||||
|     // Get image | ||||
|     let image_path = match r.has_file("image") { | ||||
|     let file = match r.has_file("image") { | ||||
|         false => None, | ||||
|         true => Some(r.save_post_image("image", "conversations", 1200, 1200)?) | ||||
|         true => { | ||||
|             let path = r.save_post_image("image", "conversations", 1200, 1200)?; | ||||
|             Some(ConversationMessageFile { | ||||
|                 path: path.clone(), | ||||
|                 size: std::fs::metadata(&path)?.len(), | ||||
|                 name: "picture.png".to_string(), | ||||
|                 thumbnail: Some(r.save_post_image("image", "conversations", 50, 50)?), | ||||
|                 r#type: "image/png".to_string(), | ||||
|             }) | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     if image_path == None && message.len() < 3 { | ||||
|         r.bad_request("Message is empty!".to_string())?; | ||||
|     } | ||||
|     let message = if let None = file { | ||||
|         Some(r.post_string_without_html("message", MIN_CONVERSATION_MESSAGE_LENGTH, true)?) | ||||
|     } else { | ||||
|         None | ||||
|     }; | ||||
|  | ||||
|     conversations_helper::send_message(&NewConversationMessage { | ||||
|         user_id: r.user_id()?, | ||||
|         conv_id, | ||||
|         user_id: Some(r.user_id()?), | ||||
|         conv_id: conv.conv_id, | ||||
|         message, | ||||
|         image_path, | ||||
|         file, | ||||
|         server_message: None, | ||||
|     })?; | ||||
|  | ||||
|     r.success("Conversation message was successfully sent!") | ||||
| @@ -307,9 +289,14 @@ pub fn list_unread(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|  | ||||
| /// Delete a conversation | ||||
| pub fn delete_conversation(r: &mut HttpRequestHandler) -> RequestResult { | ||||
|     let conv_id = r.post_conv_id("conversationID")?; | ||||
|     let conv_membership = r.post_conv("conversationID")?; | ||||
|     let conv = conversations_helper::get_single(conv_membership.conv_id)?; | ||||
|  | ||||
|     conversations_helper::remove_user_from_conversation(&r.user_id()?, conv_id)?; | ||||
|     if conv.is_managed() { | ||||
|         r.bad_request("This conversation is managed, it can not be deleted by this way!".to_string())?; | ||||
|     } | ||||
|  | ||||
|     conversations_helper::remove_user_from_conversation(&r.user_id()?, &conv, r.user_id_ref()?)?; | ||||
|  | ||||
|     r.success("The conversation has been deleted") | ||||
| } | ||||
| @@ -362,7 +349,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res { | ||||
|             user_ws_controller::send_message_to_specific_connections( | ||||
|                 |f| f.conversations.contains(&msg.conv_id), | ||||
|                 |_| UserWsMessage::no_id_message("new_conv_message", ConversationMessageAPI::new(msg)), | ||||
|                 Some(|conn: &UserWsConnection| conversations_helper::mark_user_seen(msg.conv_id, conn.user_id())), | ||||
|                 Some(|conn: &UserWsConnection| conversations_helper::mark_user_seen(msg.conv_id, conn.user_id(), msg.id)), | ||||
|             )?; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ use crate::data::base_request_handler::BaseRequestHandler; | ||||
| use crate::data::error::Res; | ||||
| use crate::data::post::PostAccessLevel; | ||||
| use crate::data::user_ws_request_handler::UserWsRequestHandler; | ||||
| use crate::data::conversation::ConvID; | ||||
|  | ||||
| /// Update incognito status of the connection | ||||
| pub fn set_incognito(r: &mut UserWsRequestHandler) -> Res { | ||||
| @@ -17,14 +18,14 @@ pub fn set_incognito(r: &mut UserWsRequestHandler) -> Res { | ||||
|  | ||||
| /// Register a conversation | ||||
| pub fn register_conv(r: &mut UserWsRequestHandler) -> Res { | ||||
|     let conv_id = r.post_conv_id("convID")?; | ||||
|     let conv_id = r.post_conv("convID")?.conv_id; | ||||
|     r.update_conn(|c| { c.conversations.insert(conv_id); })?; | ||||
|     r.success("ok") | ||||
| } | ||||
|  | ||||
| /// Un-register a conversation | ||||
| pub fn unregister_conv(r: &mut UserWsRequestHandler) -> Res { | ||||
|     let conv_id = r.post_u64("convID")?; | ||||
|     let conv_id = ConvID::new(r.post_u64("convID")?); | ||||
|     r.update_conn(|c| { c.conversations.remove(&conv_id); })?; | ||||
|     r.success("ok") | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| use std::collections::{HashMap, HashSet}; | ||||
|  | ||||
| use crate::data::comment::Comment; | ||||
| use crate::data::conversation::Conversation; | ||||
| use crate::data::conversation::{Conversation, ConvID}; | ||||
| use crate::data::conversation_message::ConversationMessage; | ||||
| use crate::data::error::ResultBoxError; | ||||
| use crate::data::friend::Friend; | ||||
| @@ -24,7 +24,7 @@ pub struct AccountExport { | ||||
|     pub survey_responses: Vec<SurveyResponse>, | ||||
|     pub all_conversation_messages: Vec<ConversationMessage>, | ||||
|     pub conversations: Vec<Conversation>, | ||||
|     pub conversation_messages: HashMap<u64, Vec<ConversationMessage>>, | ||||
|     pub conversation_messages: HashMap<ConvID, Vec<ConversationMessage>>, | ||||
|     pub friends_list: Vec<Friend>, | ||||
|     pub groups: Vec<GroupID>, | ||||
| } | ||||
| @@ -56,7 +56,7 @@ impl AccountExport { | ||||
|  | ||||
|         // Conversation members | ||||
|         for conv in &self.conversations { | ||||
|             conv.members.iter().for_each(|f| { set.insert(f.clone()); }); | ||||
|             conv.members_ids().into_iter().for_each(|f| { set.insert(f); }); | ||||
|         } | ||||
|  | ||||
|         // Conversation messages | ||||
|   | ||||
| @@ -12,7 +12,7 @@ use serde::Serialize; | ||||
| use crate::api_data::http_error::HttpError; | ||||
| use crate::constants::PASSWORD_MIN_LENGTH; | ||||
| use crate::data::comment::Comment; | ||||
| use crate::data::conversation::ConvID; | ||||
| use crate::data::conversation::{ConversationMember, ConvID}; | ||||
| use crate::data::custom_emoji::CustomEmoji; | ||||
| use crate::data::error::{ExecError, Res, ResultBoxError}; | ||||
| use crate::data::group::GroupAccessLevel; | ||||
| @@ -24,9 +24,10 @@ use crate::helpers::{account_helper, comments_helper, conversations_helper, cust | ||||
| use crate::helpers::virtual_directory_helper::VirtualDirType; | ||||
| use crate::routes::RequestResult; | ||||
| use crate::utils::pdf_utils::is_valid_pdf; | ||||
| use crate::utils::string_utils::{check_emoji_code, check_string_before_insert, check_url, remove_html_nodes}; | ||||
| use crate::utils::string_utils::{check_emoji_code, check_string_before_insert, check_url, remove_html_nodes, check_html_color}; | ||||
| use crate::utils::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path}; | ||||
| use crate::utils::virtual_directories_utils; | ||||
| use std::collections::HashSet; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| struct SuccessMessage { | ||||
| @@ -239,6 +240,18 @@ pub trait BaseRequestHandler { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Get a string included in the request. If none found, or if string is empty, returns [None] | ||||
|     fn post_string_optional(&mut self, name: &str) -> Option<String> { | ||||
|         match self.post_parameter_opt(name) { | ||||
|             Some(RequestValue::String(s)) => { | ||||
|                 if s.is_empty() { | ||||
|                     None | ||||
|                 } else { Some(s.to_string()) } | ||||
|             } | ||||
|             _ => None | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Check out whether a file was included in the request or not | ||||
|     fn has_file(&self, name: &str) -> bool { | ||||
|         self.post_parameter_opt(name) | ||||
| @@ -420,6 +433,22 @@ pub trait BaseRequestHandler { | ||||
|         Ok(user_id) | ||||
|     } | ||||
|  | ||||
|     /// Get a list of users ID included in the request | ||||
|     fn post_users_id(&mut self, name: &str) -> ResultBoxError<HashSet<UserID>> { | ||||
|         let users = self.post_numbers_list(name, 1)? | ||||
|             .iter() | ||||
|             .map(|u| UserID::new(u.clone())) | ||||
|             .collect::<HashSet<UserID>>(); | ||||
|  | ||||
|         for user in &users { | ||||
|             if !user_helper::exists(user)? { | ||||
|                 self.not_found(format!("User {} not found!", user.id()))?; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Ok(users) | ||||
|     } | ||||
|  | ||||
|     /// Get the ID of a friend included in a POST request | ||||
|     /// | ||||
|     /// *Note :* This function does not check whether the user exists or not before checking if the | ||||
| @@ -461,14 +490,14 @@ pub trait BaseRequestHandler { | ||||
|     } | ||||
|  | ||||
|     /// Get & return the ID of the conversation included in the POST request | ||||
|     fn post_conv_id(&mut self, name: &str) -> ResultBoxError<ConvID> { | ||||
|         let conv_id = self.post_u64(name)?; | ||||
|     fn post_conv(&mut self, name: &str) -> ResultBoxError<ConversationMember> { | ||||
|         let conv_id = ConvID::new(self.post_u64(name)?); | ||||
|         let membership = self.ok_or_forbidden( | ||||
|             conversations_helper::get_user_membership(&self.user_id()?, conv_id), | ||||
|             &format!("You do not belong to conversation {} !", conv_id.id()), | ||||
|         )?; | ||||
|  | ||||
|         if !conversations_helper::does_user_belongs_to(&self.user_id()?, conv_id)? { | ||||
|             self.forbidden(format!("You do not belong to conversation {} !", conv_id))?; | ||||
|         } | ||||
|  | ||||
|         Ok(conv_id) | ||||
|         Ok(membership) | ||||
|     } | ||||
|  | ||||
|     /// Get the ID | ||||
| @@ -624,4 +653,20 @@ pub trait BaseRequestHandler { | ||||
|  | ||||
|         Ok(info) | ||||
|     } | ||||
|  | ||||
|     /// Get a color included in the request | ||||
|     fn post_color_opt(&mut self, field: &str) -> Res<Option<String>> { | ||||
|         let color = self.post_string_optional(field); | ||||
|  | ||||
|         match color { | ||||
|             None => Ok(None), | ||||
|             Some(color) => { | ||||
|                 if !check_html_color(&color) { | ||||
|                     self.bad_request(format!("Invalid color specified in '{}' !", field))?; | ||||
|                 } | ||||
|  | ||||
|                 Ok(Some(color)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,20 +2,94 @@ | ||||
| //! | ||||
| //! @author Pierre Hubert | ||||
|  | ||||
| use crate::data::group_id::GroupID; | ||||
| use crate::data::user::UserID; | ||||
|  | ||||
| pub type ConvID = u64; | ||||
| #[derive(Copy, Debug, PartialEq, Eq, Clone, Hash)] | ||||
| pub struct ConvID(u64); | ||||
|  | ||||
| #[derive(Debug, PartialEq, Eq)] | ||||
| impl ConvID { | ||||
|     pub fn new(id: u64) -> Self { | ||||
|         ConvID(id) | ||||
|     } | ||||
|  | ||||
|     pub fn id(&self) -> u64 { | ||||
|         self.0.clone() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct ConversationMember { | ||||
|     pub member_id: u64, | ||||
|     pub conv_id: ConvID, | ||||
|     pub user_id: UserID, | ||||
|     pub added_on: u64, | ||||
|     pub following: bool, | ||||
|     pub is_admin: bool, | ||||
|     pub last_message_seen: u64, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Conversation { | ||||
|     pub id: ConvID, | ||||
|     pub owner_id: UserID, | ||||
|     pub name: Option<String>, | ||||
|     pub members: Vec<UserID>, | ||||
|     pub color: Option<String>, | ||||
|     pub background: Option<String>, | ||||
|     pub creation_time: u64, | ||||
|     pub group_id: Option<GroupID>, | ||||
|     pub can_everyone_add_members: bool, | ||||
|     pub last_active: u64, | ||||
|     pub time_create: u64, | ||||
|     pub last_activity: u64, | ||||
|     pub members: Vec<ConversationMember>, | ||||
| } | ||||
|  | ||||
|     pub following: bool, | ||||
|     pub saw_last_message: bool, | ||||
| impl PartialEq for Conversation { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         self.id.eq(&other.id) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Eq for Conversation {} | ||||
|  | ||||
| impl Conversation { | ||||
|     /// Get the IDs of the members in the conversation | ||||
|     pub fn members_ids(&self) -> Vec<UserID> { | ||||
|         self.members.iter().map(|m| m.user_id.clone()).collect() | ||||
|     } | ||||
|  | ||||
|     /// Check out whether this conversation is managed or not | ||||
|     pub fn is_managed(&self) -> bool { | ||||
|         self.group_id.is_some() | ||||
|     } | ||||
|  | ||||
|     /// Check out whether a given user is an admin of a conversation or not | ||||
|     pub fn is_admin(&self, user_id: &UserID) -> bool { | ||||
|         self | ||||
|             .members | ||||
|             .iter() | ||||
|             .any(|m| m.user_id == user_id && m.is_admin) | ||||
|     } | ||||
|  | ||||
|     /// Check out whether a user is the last administrator of a conversation or not | ||||
|     pub fn is_last_admin(&self, user_id: &UserID) -> bool { | ||||
|         let admins: Vec<&ConversationMember> = self.members | ||||
|             .iter() | ||||
|             .filter(|m| m.is_admin) | ||||
|             .collect(); | ||||
|  | ||||
|         admins.len() == 1 && admins[0].user_id == user_id | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Structure used to update the list of members of the conversation | ||||
| pub struct ConversationMemberSetting { | ||||
|     pub user_id: UserID, | ||||
|     pub set_admin: bool, | ||||
| } | ||||
|  | ||||
| /// Structure used to update conversation settings | ||||
| pub struct NewConversationSettings { | ||||
|     pub conv_id: ConvID, | ||||
|     pub color: Option<String>, | ||||
|     pub name: Option<String>, | ||||
|     pub can_everyone_add_members: bool, | ||||
| } | ||||
| @@ -6,6 +6,7 @@ use std::collections::HashSet; | ||||
|  | ||||
| use crate::data::error::{ExecError, Res}; | ||||
| use crate::data::user::UserID; | ||||
| use crate::data::conversation::ConvID; | ||||
|  | ||||
| pub type ConvMessageID = u64; | ||||
|  | ||||
| @@ -101,7 +102,7 @@ impl ConversationServerMessageType { | ||||
| pub struct ConversationMessage { | ||||
|     pub id: ConvMessageID, | ||||
|     pub time_sent: u64, | ||||
|     pub conv_id: u64, | ||||
|     pub conv_id: ConvID, | ||||
|     pub user_id: Option<UserID>, | ||||
|     pub message: Option<String>, | ||||
|     pub server_message: Option<ConversationServerMessageType>, | ||||
|   | ||||
| @@ -3,11 +3,15 @@ | ||||
| //! @author Pierre Huber | ||||
|  | ||||
| use crate::data::user::UserID; | ||||
| use crate::data::group_id::GroupID; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct NewConversation { | ||||
|     pub owner_id: UserID, | ||||
|     pub name: Option<String>, | ||||
|     pub group_id: Option<GroupID>, | ||||
|     pub color: Option<String>, | ||||
|     pub background: Option<String>, | ||||
|     pub owner_following: bool, | ||||
|     pub members: Vec<UserID>, | ||||
|     pub can_everyone_add_members: bool | ||||
|   | ||||
| @@ -3,11 +3,14 @@ | ||||
| //! @author Pierre Hubert | ||||
|  | ||||
| use crate::data::user::UserID; | ||||
| use crate::data::conversation::ConvID; | ||||
| use crate::data::conversation_message::{ConversationMessageFile, ConversationServerMessageType}; | ||||
|  | ||||
| /// Information about a new conversation message | ||||
| pub struct NewConversationMessage { | ||||
|     pub user_id: UserID, | ||||
|     pub conv_id: u64, | ||||
|     pub message: String, | ||||
|     pub image_path: Option<String> | ||||
|     pub user_id: Option<UserID>, | ||||
|     pub conv_id: ConvID, | ||||
|     pub message: Option<String>, | ||||
|     pub file: Option<ConversationMessageFile>, | ||||
|     pub server_message: Option<ConversationServerMessageType> | ||||
| } | ||||
| @@ -20,7 +20,7 @@ impl UserMembership { | ||||
|         match self { | ||||
|             UserMembership::Group(_, last_active) => *last_active, | ||||
|             UserMembership::Friend(f) => f.last_activity_time, | ||||
|             UserMembership::Conversation(c) => c.last_active, | ||||
|             UserMembership::Conversation(c) => c.last_activity, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| //! @author Pierre Hubert | ||||
|  | ||||
| use crate::constants::database_tables_names::{CONV_LIST_TABLE, CONV_MEMBERS_TABLE, CONV_MESSAGES_TABLE}; | ||||
| use crate::data::conversation::Conversation; | ||||
| use crate::data::conversation::{Conversation, ConversationMember, ConversationMemberSetting, ConvID, NewConversationSettings}; | ||||
| use crate::data::conversation_message::{ConversationMessage, ConversationMessageFile, ConversationServerMessageType}; | ||||
| use crate::data::error::{ExecError, Res, ResultBoxError}; | ||||
| use crate::data::new_conversation::NewConversation; | ||||
| @@ -11,57 +11,81 @@ use crate::data::new_conversation_message::NewConversationMessage; | ||||
| use crate::data::unread_conversation::UnreadConversation; | ||||
| use crate::data::user::{User, UserID}; | ||||
| use crate::helpers::{database, events_helper}; | ||||
| use crate::helpers::database::InsertQuery; | ||||
| use crate::helpers::database::{InsertQuery, UpdateInfo}; | ||||
| use crate::helpers::events_helper::Event; | ||||
| use crate::utils::date_utils::time; | ||||
| 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<u64> { | ||||
| pub fn create(conv: &NewConversation) -> Res<ConvID> { | ||||
|     // Create the conversation in the main table | ||||
|     let conv_id = InsertQuery::new(CONV_LIST_TABLE) | ||||
|         .add_user_id("user_id", &conv.owner_id) | ||||
|         .add_str("name", conv.name.clone().unwrap_or(String::new()).as_str()) | ||||
|         .add_u64("last_active", time()) | ||||
|         .add_u64("last_activity", time()) | ||||
|         .add_u64("creation_time", time()) | ||||
|         .add_opt_str("color", Option::from(&conv.color)) | ||||
|         .add_opt_str("background", Option::from(&conv.background)) | ||||
|         .add_legacy_bool("can_everyone_add_members", conv.can_everyone_add_members) | ||||
|         .insert()?.ok_or(ExecError::new("missing result conv id!"))?; | ||||
|         .add_opt_group_id("group_id", conv.group_id.clone()) | ||||
|         .insert()? | ||||
|         .map(|i| ConvID::new(i)) | ||||
|         .ok_or(ExecError::new("missing result conv id!"))?; | ||||
|  | ||||
|     // Add the members to the conversation | ||||
|     for member in &conv.members { | ||||
|  | ||||
|         // Check following status of the member | ||||
|         let mut follow = true; | ||||
|         let mut admin = false; | ||||
|         if member.eq(&conv.owner_id) { | ||||
|             follow = conv.owner_following; | ||||
|             admin = true; | ||||
|         } | ||||
|  | ||||
|         add_member(conv_id, member, follow)?; | ||||
|         add_member(conv_id, member, follow, admin)?; | ||||
|     } | ||||
|  | ||||
|     Ok(conv_id) | ||||
| } | ||||
|  | ||||
| /// Add a member to a conversation | ||||
| pub fn add_member(conv_id: u64, user_id: &UserID, following: bool) -> ResultBoxError<()> { | ||||
| pub fn add_member(conv_id: ConvID, user_id: &UserID, following: bool, admin: bool) -> Res { | ||||
|     InsertQuery::new(CONV_MEMBERS_TABLE) | ||||
|         .add_u64("conv_id", conv_id) | ||||
|         .add_conv_id("conv_id", conv_id) | ||||
|         .add_user_id("user_id", user_id) | ||||
|         .add_u64("time_add", time()) | ||||
|         .add_u64("added_on", time()) | ||||
|         .add_legacy_bool("following", following) | ||||
|         .add_legacy_bool("saw_last_message", true) | ||||
|         .add_legacy_bool("is_admin", admin) | ||||
|         .add_u64("last_message_seen", 0) | ||||
|         .insert()?; | ||||
|  | ||||
|     // TODO : create a message | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Remove a member from a conversation | ||||
| pub fn remove_member(conv_id: u64, user_id: &UserID) -> ResultBoxError<()> { | ||||
|     database::DeleteQuery::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
| /// Update admin status of a member for a conversation | ||||
| pub fn set_admin(conv_id: &ConvID, user_id: &UserID, admin: bool) -> Res { | ||||
|     UpdateInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_user_id("user_id", user_id) | ||||
|         .cond_conv_id("conv_id", conv_id.clone()) | ||||
|         .set_legacy_bool("is_admin", admin) | ||||
|         .exec() | ||||
| } | ||||
|  | ||||
| /// Remove a member from a conversation | ||||
| pub fn remove_member(conv_id: ConvID, user_id: &UserID) -> ResultBoxError<()> { | ||||
|     database::DeleteQuery::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_conv_id("conv_id", conv_id) | ||||
|         .cond_user_id("user_id", user_id) | ||||
|         .exec()?; | ||||
|  | ||||
|     // TODO : create a message | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Get the list of conversations of a specific user | ||||
| pub fn get_list_user(user_id: &UserID) -> ResultBoxError<Vec<Conversation>> { | ||||
|     database::QueryInfo::new(CONV_LIST_TABLE) | ||||
| @@ -71,67 +95,46 @@ pub fn get_list_user(user_id: &UserID) -> ResultBoxError<Vec<Conversation>> { | ||||
|         .join(CONV_MEMBERS_TABLE, "u", "l.id = u.conv_id") | ||||
|  | ||||
|         // Specify selected fields | ||||
|         .add_field("*") | ||||
|         .add_field("l.id as id") | ||||
|         .add_field("l.user_id as owner_id") | ||||
|         .add_field("l.*") | ||||
|  | ||||
|         // Filter query | ||||
|         .cond_user_id("u.user_id", user_id) | ||||
|  | ||||
|         // Sort results | ||||
|         .set_order("l.last_active DESC") | ||||
|         .set_order("l.last_activity DESC") | ||||
|  | ||||
|         // Execute query | ||||
|         .exec(db_to_conversation_info) | ||||
| } | ||||
|  | ||||
| /// Get information about a single conversation | ||||
| pub fn get_single(conv_id: u64, user_id: &UserID) -> ResultBoxError<Conversation> { | ||||
| pub fn get_single(conv_id: ConvID) -> ResultBoxError<Conversation> { | ||||
|     // Tables | ||||
|     database::QueryInfo::new(CONV_LIST_TABLE) | ||||
|         .alias("l") | ||||
|         .join(CONV_MEMBERS_TABLE, "u", "l.id = u.conv_id") | ||||
|  | ||||
|         // Fields | ||||
|         .add_field("*") | ||||
|         .add_field("l.id as id") | ||||
|         .add_field("l.user_id as owner_id") | ||||
|  | ||||
|         // Conditions | ||||
|         .cond_u64("l.id", conv_id) | ||||
|         .cond_user_id("u.user_id", user_id) | ||||
|  | ||||
|         .cond_conv_id("id", conv_id) | ||||
|         .query_row(db_to_conversation_info) | ||||
| } | ||||
|  | ||||
| /// Get the list of members of a conversation | ||||
| pub fn get_list_members(conv_id: u64) -> ResultBoxError<Vec<UserID>> { | ||||
| pub fn get_list_members(conv_id: ConvID) -> Res<Vec<ConversationMember>> { | ||||
|     database::QueryInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
|         .add_field("user_id") | ||||
|         .exec(|res| res.get_user_id("user_id")) | ||||
|         .cond_conv_id("conv_id", conv_id) | ||||
|         .exec(db_to_conversation_member) | ||||
| } | ||||
|  | ||||
| /// Check if a user belongs to a conversation or not | ||||
| pub fn does_user_belongs_to(user_id: &UserID, conv_id: u64) -> ResultBoxError<bool> { | ||||
|     Ok(database::QueryInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
| pub fn get_user_membership(user_id: &UserID, conv_id: ConvID) -> Res<ConversationMember> { | ||||
|     database::QueryInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_conv_id("conv_id", conv_id) | ||||
|         .cond_user_id("user_id", user_id) | ||||
|         .exec_count()? > 0) | ||||
|         .query_row(db_to_conversation_member) | ||||
| } | ||||
|  | ||||
| /// Check out wheter a user is the moderator of a conversation or not | ||||
| pub fn is_user_moderator(user_id: &UserID, conv_id: u64) -> ResultBoxError<bool> { | ||||
|     Ok(database::QueryInfo::new(CONV_LIST_TABLE) | ||||
|         .cond_u64("id", conv_id) | ||||
|         .cond_user_id("user_id", user_id) | ||||
|         .exec_count()? > 0) | ||||
| } | ||||
|  | ||||
| /// Check out whether all the members of a conversation can add members to it or not | ||||
| pub fn can_everyone_add_members(conv_id: u64) -> ResultBoxError<bool> { | ||||
| pub fn can_everyone_add_members(conv_id: ConvID) -> ResultBoxError<bool> { | ||||
|     database::QueryInfo::new(CONV_LIST_TABLE) | ||||
|         .cond_u64("id", conv_id) | ||||
|         .cond_conv_id("id", conv_id) | ||||
|         .add_field("can_everyone_add_members") | ||||
|         .query_row(|f| f.get_legacy_bool("can_everyone_add_members")) | ||||
| } | ||||
| @@ -146,25 +149,28 @@ pub fn set_following(user_id: &UserID, conv_id: u64, following: bool) -> ResultB | ||||
| } | ||||
|  | ||||
| /// Set a new list of members for a given conversation | ||||
| pub fn set_members(conv_id: u64, new_list: &Vec<UserID>, can_delete: bool) -> ResultBoxError<()> { | ||||
| pub fn set_members(conv_id: ConvID, new_list: &Vec<ConversationMemberSetting>, can_delete: bool) -> ResultBoxError<()> { | ||||
|     let curr_list = get_list_members(conv_id)?; | ||||
|  | ||||
|     // Add new members | ||||
|     for member in new_list { | ||||
|         if curr_list.contains(member) { | ||||
|             continue; | ||||
|         if let Some(user) = curr_list.iter().filter(|m| m.user_id == member.user_id).next() { | ||||
|             // Check if we have to update admin state | ||||
|             if user.is_admin != member.set_admin { | ||||
|                 set_admin(&conv_id, &member.user_id, member.set_admin)?; | ||||
|             } | ||||
|         } else { | ||||
|             add_member(conv_id, &member.user_id, true, member.set_admin)?; | ||||
|         } | ||||
|  | ||||
|         add_member(conv_id, member, true)?; | ||||
|     } | ||||
|  | ||||
|     // Remove a member | ||||
|     if can_delete { | ||||
|         for member in curr_list { | ||||
|             if new_list.contains(&member) { | ||||
|             if new_list.iter().any(|m| m.user_id.eq(&member.user_id)) { | ||||
|                 continue; | ||||
|             } | ||||
|             remove_member(conv_id, &member)?; | ||||
|             remove_member(conv_id, &member.user_id)?; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -173,23 +179,17 @@ pub fn set_members(conv_id: u64, new_list: &Vec<UserID>, can_delete: bool) -> Re | ||||
| } | ||||
|  | ||||
| /// Set a new name to the conversation | ||||
| pub fn set_name(conv_id: u64, name: Option<String>) -> ResultBoxError<()> { | ||||
| pub fn set_settings(settings: NewConversationSettings) -> Res { | ||||
|     database::UpdateInfo::new(CONV_LIST_TABLE) | ||||
|         .cond_u64("id", conv_id) | ||||
|         .set_opt_str("name", name) | ||||
|         .exec() | ||||
| } | ||||
|  | ||||
| /// Specify whether any member of this conversation can invite other users to join it | ||||
| pub fn set_can_everyone_add_members(conv_id: u64, allow: bool) -> ResultBoxError<()> { | ||||
|     database::UpdateInfo::new(CONV_LIST_TABLE) | ||||
|         .cond_u64("id", conv_id) | ||||
|         .set_legacy_bool("can_everyone_add_members", allow) | ||||
|         .cond_conv_id("id", settings.conv_id) | ||||
|         .set_opt_str("name", settings.name) | ||||
|         .set_opt_str("color", settings.color) | ||||
|         .set_legacy_bool("can_everyone_add_members", settings.can_everyone_add_members) | ||||
|         .exec() | ||||
| } | ||||
|  | ||||
| /// Search for private conversation between two users | ||||
| pub fn find_private(user_1: &UserID, user_2: &UserID) -> ResultBoxError<Vec<u64>> { | ||||
| pub fn find_private(user_1: &UserID, user_2: &UserID) -> ResultBoxError<Vec<ConvID>> { | ||||
|     database::QueryInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .alias("t1") | ||||
|  | ||||
| @@ -201,7 +201,7 @@ pub fn find_private(user_1: &UserID, user_2: &UserID) -> ResultBoxError<Vec<u64> | ||||
|         .cond_user_id("t2.user_id", user_2) | ||||
|         .set_custom_where(format!("(SELECT COUNT(*) FROM {} WHERE conv_id = t1.conv_id) = 2", CONV_MEMBERS_TABLE).as_ref()) | ||||
|         .add_field("t1.conv_id AS conv_id") | ||||
|         .exec(|f| f.get_u64("conv_id")) | ||||
|         .exec(|f| f.get_conv_id("conv_id")) | ||||
| } | ||||
|  | ||||
| /// Get the last messages posted in a conversation | ||||
| @@ -247,9 +247,9 @@ pub fn get_older_messages(conv_id: u64, start_id: u64, limit: u64) -> ResultBoxE | ||||
| } | ||||
|  | ||||
| /// Get all the messages of a single user for a conversation | ||||
| pub fn get_user_messages_for_conversations(conv_id: u64, user_id: &UserID) -> ResultBoxError<Vec<ConversationMessage>> { | ||||
| pub fn get_user_messages_for_conversations(conv_id: ConvID, user_id: &UserID) -> ResultBoxError<Vec<ConversationMessage>> { | ||||
|     database::QueryInfo::new(CONV_MESSAGES_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
|         .cond_conv_id("conv_id", conv_id) | ||||
|         .cond_user_id("user_id", user_id) | ||||
|         .exec(db_to_conversation_message) | ||||
| } | ||||
| @@ -293,16 +293,16 @@ pub fn delete_all_user_messages(user_id: &UserID) -> ResultBoxError { | ||||
| /// Remove the user from all the conversations he belongs to | ||||
| pub fn delete_all_user_conversations(user_id: &UserID) -> ResultBoxError { | ||||
|     for conversation in &get_list_user(user_id)? { | ||||
|         remove_user_from_conversation(user_id, conversation.id)?; | ||||
|         remove_user_from_conversation(user_id, conversation, user_id)?; | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Get the entire list of messages of a given conversation | ||||
| pub fn get_all_messages(conv_id: u64) -> ResultBoxError<Vec<ConversationMessage>> { | ||||
| pub fn get_all_messages(conv_id: ConvID) -> ResultBoxError<Vec<ConversationMessage>> { | ||||
|     database::QueryInfo::new(CONV_MESSAGES_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
|         .cond_conv_id("conv_id", conv_id) | ||||
|         .exec(db_to_conversation_message) | ||||
| } | ||||
|  | ||||
| @@ -318,39 +318,45 @@ pub fn send_message(msg: &NewConversationMessage) -> ResultBoxError<()> { | ||||
|     let t = time(); | ||||
|  | ||||
|     // Insert the message in the database | ||||
|     let msg_id = database::InsertQuery::new(CONV_MESSAGES_TABLE) | ||||
|         .add_u64("conv_id", msg.conv_id) | ||||
|         .add_user_id("user_id", &msg.user_id) | ||||
|         .add_u64("time_insert", t) | ||||
|         .add_str("message", msg.message.as_str()) | ||||
|         .add_opt_str("image_path", msg.image_path.as_ref()) | ||||
|         .insert_expect_result()?; | ||||
|     let mut msg_request = database::InsertQuery::new(CONV_MESSAGES_TABLE) | ||||
|         .add_conv_id("conv_id", msg.conv_id) | ||||
|         .add_u64("user_id", msg.user_id.as_ref().map(|u| u.id()).unwrap_or(0)) | ||||
|         .add_u64("time_sent", t); | ||||
|  | ||||
|     if let Some(server_msg) = &msg.server_message { | ||||
|         msg_request = msg_request.add_str("message", &server_msg.to_db()); | ||||
|     } else if let Some(message) = &msg.message { | ||||
|         msg_request = msg_request.add_str("message", message); | ||||
|     } | ||||
|  | ||||
|     if let Some(file) = &msg.file { | ||||
|         msg_request = msg_request.add_str("file_path", &file.path) | ||||
|             .add_u64("file_size", file.size) | ||||
|             .add_str("file_name", &file.name) | ||||
|             .add_str("file_type", &file.r#type) | ||||
|             .add_opt_str("file_thumbnail", Option::from(&file.thumbnail)); | ||||
|     } | ||||
|  | ||||
|     let msg_id = msg_request.insert_expect_result()?; | ||||
|  | ||||
|     // Update the last activity of the conversation | ||||
|     database::UpdateInfo::new(CONV_LIST_TABLE) | ||||
|         .cond_u64("id", msg.conv_id) | ||||
|         .set_u64("last_active", t) | ||||
|         .cond_conv_id("id", msg.conv_id) | ||||
|         .set_u64("last_activity", t) | ||||
|         .exec()?; | ||||
|  | ||||
|     // Get the list of users to notify after the update | ||||
|     let list_to_notify = database::QueryInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", msg.conv_id) | ||||
|         .cond_legacy_bool("saw_last_message", true) | ||||
|         .cond_conv_id("conv_id", msg.conv_id) | ||||
|         .cond_legacy_bool("following", true) | ||||
|         .set_custom_where("user_id != ?") | ||||
|         .add_custom_where_argument_user_id(&msg.user_id) | ||||
|         .add_custom_where_argument_user_id(msg.user_id.as_ref().unwrap_or(&UserID::invalid())) | ||||
|         .exec(|r| r.get_user_id("user_id"))?; | ||||
|  | ||||
|     // Mark all the users of the conversation as unread | ||||
|     database::UpdateInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", msg.conv_id) | ||||
|         .cond_legacy_bool("saw_last_message", true) | ||||
|  | ||||
|         .custom_where("user_id != ?") | ||||
|         .add_custom_where_arg_u64(msg.user_id.id()) | ||||
|  | ||||
|         .set_legacy_bool("saw_last_message", false) | ||||
|         .exec()?; | ||||
|     // Mark the user has seen his message | ||||
|     if let Some(user_id) = &msg.user_id { | ||||
|         mark_user_seen(msg.conv_id, user_id, msg_id)?; | ||||
|     } | ||||
|  | ||||
|     // Send an event (updated_number_unread_conversations) | ||||
|     events_helper::propagate_event(&Event::UpdatedNumberUnreadConversations(&list_to_notify))?; | ||||
| @@ -419,34 +425,32 @@ pub fn get_list_unread(user_id: &UserID) -> ResultBoxError<Vec<UnreadConversatio | ||||
|  | ||||
|         .cond_user_id("users.user_id", user_id) | ||||
|         .cond_legacy_bool("users.following", true) | ||||
|         .cond_legacy_bool("users.saw_last_message", false) | ||||
|  | ||||
|         .set_custom_where("list.last_active = messages.time_insert") | ||||
|         .set_custom_where("list.last_activity = messages.time_insert AND user.last_message_seen < messages.id") | ||||
|  | ||||
|         .set_order("list.last_active DESC") | ||||
|         .set_order("list.last_activity DESC") | ||||
|  | ||||
|         .add_field("messages.conv_id") | ||||
|         .add_field("name") | ||||
|         .add_field("last_active") | ||||
|         .add_field("last_activity") | ||||
|         .add_field("messages.user_id") | ||||
|         .add_field("message") | ||||
|  | ||||
|         .exec(|res| Ok(UnreadConversation { | ||||
|             id: res.get_u64("conv_id")?, | ||||
|             name: res.get_optional_str("name")?, | ||||
|             last_active: res.get_u64("last_active")?, | ||||
|             last_active: res.get_u64("last_activity")?, | ||||
|             user_id: res.get_user_id("user_id")?, | ||||
|             message: res.get_str("message")?, | ||||
|         })) | ||||
| } | ||||
|  | ||||
| /// Indicate that a user has seen the last messages of a conversation | ||||
| pub fn mark_user_seen(conv_id: u64, user_id: &UserID) -> ResultBoxError<()> { | ||||
| pub fn mark_user_seen(conv_id: ConvID, user_id: &UserID, last_msg: u64) -> ResultBoxError<()> { | ||||
|     database::UpdateInfo::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
|         .cond_conv_id("conv_id", conv_id) | ||||
|         .cond_user_id("user_id", user_id) | ||||
|         .cond_legacy_bool("saw_last_message", false) | ||||
|         .set_legacy_bool("saw_last_message", true) | ||||
|         .set_u64("last_message_seen", last_msg) | ||||
|         .exec()?; | ||||
|  | ||||
|     // Push an event (updated_number_unread_conversations) | ||||
| @@ -456,36 +460,41 @@ pub fn mark_user_seen(conv_id: u64, user_id: &UserID) -> ResultBoxError<()> { | ||||
| } | ||||
|  | ||||
| /// Remove a user from a conversation | ||||
| pub fn remove_user_from_conversation(user_id: &UserID, conv_id: u64) -> ResultBoxError<()> { | ||||
|     if is_user_moderator(user_id, conv_id)? { | ||||
|         delete_conversation(conv_id) | ||||
| pub fn remove_user_from_conversation(user_id: &UserID, conv: &Conversation, remover: &UserID) -> ResultBoxError<()> { | ||||
|     if conv.is_last_admin(user_id) { | ||||
|         delete_conversation(conv) | ||||
|     } else { | ||||
|         delete_member(user_id, conv_id) | ||||
|         delete_member(user_id, conv.id, remover) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Remove permanently a conversation | ||||
| pub fn delete_conversation(conv_id: u64) -> ResultBoxError<()> { | ||||
| pub fn delete_conversation(conv: &Conversation) -> ResultBoxError<()> { | ||||
|     // Delete all the messages of the conversations | ||||
|     for message in get_all_messages(conv_id)? { | ||||
|     for message in get_all_messages(conv.id)? { | ||||
|         delete_message(&message)?; | ||||
|     } | ||||
|  | ||||
|     // Delete all the members of the conversation | ||||
|     database::DeleteQuery::new(CONV_MEMBERS_TABLE) | ||||
|         .cond_u64("conv_id", conv_id) | ||||
|         .cond_conv_id("conv_id", conv.id) | ||||
|         .exec()?; | ||||
|  | ||||
|     // Delete associated background image, if any | ||||
|     if let Some(image) = &conv.background { | ||||
|         delete_user_data_file_if_exists(image)?; | ||||
|     } | ||||
|  | ||||
|     // Delete the conversation entry itself | ||||
|     database::DeleteQuery::new(CONV_LIST_TABLE) | ||||
|         .cond_u64("id", conv_id) | ||||
|         .cond_conv_id("id", conv.id) | ||||
|         .exec()?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Delete a conversation membership | ||||
| pub fn delete_member(user_id: &UserID, conv_id: u64) -> ResultBoxError<()> { | ||||
| pub fn delete_member(user_id: &UserID, conv_id: ConvID, _remover: &UserID) -> ResultBoxError<()> { | ||||
|     for msg in get_user_messages_for_conversations(conv_id, user_id)? { | ||||
|         delete_message(&msg)?; | ||||
|     } | ||||
| @@ -507,17 +516,30 @@ pub fn is_message_owner(user_id: &UserID, message_id: u64) -> ResultBoxError<boo | ||||
|  | ||||
| /// Turn a database entry into a ConversationInfo object | ||||
| fn db_to_conversation_info(row: &database::RowResult) -> ResultBoxError<Conversation> { | ||||
|     let conv_id = row.get_u64("id")?; | ||||
|     let conv_id = row.get_conv_id("id")?; | ||||
|     Ok(Conversation { | ||||
|         id: conv_id, | ||||
|         owner_id: row.get_user_id("owner_id")?, | ||||
|         color: row.get_optional_str("color")?, | ||||
|         background: row.get_optional_str("background")?, | ||||
|         name: row.get_optional_str("name")?, | ||||
|         members: get_list_members(conv_id)?, | ||||
|         can_everyone_add_members: row.get_legacy_bool("can_everyone_add_members")?, | ||||
|         last_active: row.get_u64("last_active")?, | ||||
|         time_create: row.get_u64("time_add")?, | ||||
|         last_activity: row.get_u64("last_activity")?, | ||||
|         creation_time: row.get_u64("creation_time")?, | ||||
|         group_id: row.get_optional_group_id("group_id")?, | ||||
|     }) | ||||
| } | ||||
|  | ||||
| /// Turn a database entry into a ConversationMember object | ||||
| fn db_to_conversation_member(row: &database::RowResult) -> Res<ConversationMember> { | ||||
|     Ok(ConversationMember { | ||||
|         member_id: row.get_u64("id")?, | ||||
|         conv_id: row.get_conv_id("conv_id")?, | ||||
|         user_id: row.get_user_id("user_id")?, | ||||
|         added_on: row.get_u64("added_on")?, | ||||
|         following: row.get_legacy_bool("following")?, | ||||
|         saw_last_message: row.get_legacy_bool("saw_last_message")?, | ||||
|         is_admin: row.get_legacy_bool("is_admin")?, | ||||
|         last_message_seen: row.get_u64("last_message_seen")?, | ||||
|     }) | ||||
| } | ||||
|  | ||||
| @@ -529,10 +551,10 @@ fn db_to_conversation_message(row: &database::RowResult) -> ResultBoxError<Conve | ||||
|         false => Some(row.get_user_id("user_id")?) | ||||
|     }; | ||||
|  | ||||
|     let file = match row.is_null_or_empty("filepath")? { | ||||
|     let file = match row.is_null_or_empty("file_path")? { | ||||
|         true => None, | ||||
|         false => Some(ConversationMessageFile { | ||||
|             path: row.get_str("filepath")?, | ||||
|             path: row.get_str("file_path")?, | ||||
|             size: row.get_u64("file_size")?, | ||||
|             name: row.get_str("file_name")?, | ||||
|             thumbnail: row.get_optional_str("file_thumbnail")?, | ||||
| @@ -553,7 +575,7 @@ fn db_to_conversation_message(row: &database::RowResult) -> ResultBoxError<Conve | ||||
|     Ok(ConversationMessage { | ||||
|         id: row.get_u64("id")?, | ||||
|         time_sent: row.get_u64("time_sent")?, | ||||
|         conv_id: row.get_u64("conv_id")?, | ||||
|         conv_id: row.get_conv_id("conv_id")?, | ||||
|         user_id, | ||||
|         message, | ||||
|         server_message, | ||||
|   | ||||
| @@ -12,6 +12,7 @@ use crate::data::config::{conf, DatabaseConfig}; | ||||
| use crate::data::error::{ExecError, ResultBoxError}; | ||||
| use crate::data::group_id::GroupID; | ||||
| use crate::data::user::UserID; | ||||
| use crate::data::conversation::ConvID; | ||||
|  | ||||
| /// Database access helper | ||||
| /// | ||||
| @@ -174,6 +175,11 @@ impl QueryInfo { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn cond_conv_id(mut self, key: &str, val: ConvID) -> QueryInfo { | ||||
|         self.conditions.insert(key.to_string(), mysql::Value::from(val.id())); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn cond_legacy_bool(mut self, key: &str, val: bool) -> QueryInfo { | ||||
|         let val = match val { | ||||
|             true => 1, | ||||
| @@ -358,6 +364,19 @@ impl<'a> RowResult<'a> { | ||||
|         Ok(GroupID::new(self.get_u64(name)?)) | ||||
|     } | ||||
|  | ||||
|     /// Get the optional ID of a group included in the response | ||||
|     pub fn get_optional_group_id(&self, name: &str) -> ResultBoxError<Option<GroupID>> { | ||||
|         Ok(match self.get_optional_u64(name)? { | ||||
|             None | Some(0) => None, | ||||
|             Some(id) => Some(GroupID::new(id)) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Get the ID of a conversation included in the response | ||||
|     pub fn get_conv_id(&self, name: &str) -> ResultBoxError<ConvID> { | ||||
|         Ok(ConvID::new(self.get_u64(name)?)) | ||||
|     } | ||||
|  | ||||
|     /// Find a string included in the request | ||||
|     pub fn get_str(&self, name: &str) -> Result<String, Box<dyn Error>> { | ||||
|         let value = self.row.get_opt(self.find_col(name)?); | ||||
| @@ -588,6 +607,19 @@ impl InsertQuery { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Add an optional number. If None, Null will be inserted | ||||
|     pub fn add_opt_u64(mut self, key: &str, value: Option<u64>) -> InsertQuery { | ||||
|         self.values.insert(key.to_string(), value | ||||
|             .map(|u| Value::UInt(u)) | ||||
|             .unwrap_or(Value::NULL)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Add an optional number. If None, Null will be inserted | ||||
|     pub fn add_opt_group_id(self, key: &str, value: Option<GroupID>) -> InsertQuery { | ||||
|         self.add_opt_u64(key, value.map(|u| u.id())) | ||||
|     } | ||||
|  | ||||
|     /// Add an integer | ||||
|     pub fn add_i64(mut self, key: &str, value: i64) -> InsertQuery { | ||||
|         self.values.insert(key.to_string(), Value::from(value)); | ||||
| @@ -619,6 +651,12 @@ impl InsertQuery { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn add_conv_id(mut self, key: &str, value: ConvID) -> InsertQuery { | ||||
|         self.values.insert(key.to_string(), Value::from(value.id())); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     /// Legacy database boolean (1 = true / 0 = false) | ||||
|     pub fn add_legacy_bool(mut self, key: &str, value: bool) -> InsertQuery { | ||||
| @@ -758,6 +796,11 @@ impl DeleteQuery { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn cond_conv_id(mut self, key: &str, value: ConvID) -> DeleteQuery { | ||||
|         self.conditions.insert(key.to_string(), Value::from(value.id())); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Execute the delete query | ||||
|     pub fn exec(self) -> ResultBoxError<()> { | ||||
|         delete(self) | ||||
| @@ -832,6 +875,13 @@ impl UpdateInfo { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Filter with a conversation id | ||||
|     pub fn cond_conv_id(mut self, name: &str, val: ConvID) -> UpdateInfo { | ||||
|         self.cond.insert(name.to_string(), Value::UInt(val.id())); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// Filter with an unsigned integer | ||||
|     pub fn cond_u64(mut self, name: &str, val: u64) -> UpdateInfo { | ||||
|         self.cond.insert(name.to_string(), Value::UInt(val)); | ||||
|   | ||||
| @@ -196,7 +196,6 @@ pub fn get_routes() -> Vec<Route> { | ||||
|         Route::post("/conversations/getInfosOne", Box::new(conversations_controller::get_single)), | ||||
|         Route::post("/conversations/updateSettings", Box::new(conversations_controller::update_settings)), | ||||
|         Route::post("/conversations/getPrivate", Box::new(conversations_controller::find_private)), | ||||
|         Route::post("/conversations/refresh", Box::new(conversations_controller::refresh_list)), | ||||
|         Route::post("/conversations/refresh_single", Box::new(conversations_controller::refresh_single)), | ||||
|         Route::post("/conversations/get_older_messages", Box::new(conversations_controller::get_older_messages)), | ||||
|         Route::post("/conversations/sendMessage", Box::new(conversations_controller::send_message)), | ||||
| @@ -205,6 +204,7 @@ pub fn get_routes() -> Vec<Route> { | ||||
|         Route::post("/conversations/delete", Box::new(conversations_controller::delete_conversation)), | ||||
|         Route::post("/conversations/updateMessage", Box::new(conversations_controller::update_message)), | ||||
|         Route::post("/conversations/deleteMessage", Box::new(conversations_controller::delete_message)), | ||||
|         // TODO : add a route to mark messages seen | ||||
|  | ||||
|  | ||||
|         // Search controller | ||||
|   | ||||
| @@ -92,4 +92,18 @@ pub fn check_youtube_id(id: &str) -> bool { | ||||
| pub fn check_emoji_code(shortcut: &str) -> bool { | ||||
|     let r = Regex::new(r"^:[a-zA-Z0-9]+:$").unwrap(); | ||||
|     r.is_match(shortcut) | ||||
| } | ||||
|  | ||||
| /// Check the validity of an HTML color | ||||
| /// | ||||
| /// ``` | ||||
| /// use comunic_server::utils::string_utils::check_emoji_code; | ||||
| /// | ||||
| /// assert_eq!(check_emoji_code("AAFF00"), true); | ||||
| /// assert_eq!(check_emoji_code("ABC"), false); | ||||
| /// assert_eq!(check_emoji_code("UUFF00"), false); | ||||
| /// ``` | ||||
| pub fn check_html_color(color: &str) -> bool { | ||||
|     let r = Regex::new(r"^:[A-F0-9]{6}:$").unwrap(); | ||||
|     r.is_match(color) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user