1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2024-11-26 15:29:21 +00:00

Continue refactoring

This commit is contained in:
Pierre HUBERT 2021-03-04 18:51:52 +01:00
parent 2cc8984dfa
commit fbf4728347
27 changed files with 527 additions and 355 deletions

View File

@ -53,7 +53,7 @@ impl AccountExportAPI {
conversations_list: ConversationAPI::for_list(&export.conversations), conversations_list: ConversationAPI::for_list(&export.conversations),
conversations_messages: export.conversation_messages conversations_messages: export.conversation_messages
.iter() .iter()
.map(|r| (r.0.clone(), ConversationMessageAPI::for_list(r.1))) .map(|r| (r.0.id(), ConversationMessageAPI::for_list(r.1)))
.collect(), .collect(),
friends_list: FriendAPI::from_list(&export.friends_list), friends_list: FriendAPI::from_list(&export.friends_list),
groups: export.groups groups: export.groups

View File

@ -17,7 +17,7 @@ pub struct CallPeerInterruptedStreamingAPI {
impl CallPeerInterruptedStreamingAPI { impl CallPeerInterruptedStreamingAPI {
pub fn new(call_id: &ConvID, peer_id: &UserID) -> Self { pub fn new(call_id: &ConvID, peer_id: &UserID) -> Self {
Self { Self {
callID: call_id.clone(), callID: call_id.id(),
peerID: peer_id.id(), peerID: peer_id.id(),
} }
} }

View File

@ -16,7 +16,7 @@ pub struct CallPeerReadyAPI {
impl CallPeerReadyAPI { impl CallPeerReadyAPI {
pub fn new(call_id: &ConvID, user_id: &UserID) -> Self { pub fn new(call_id: &ConvID, user_id: &UserID) -> Self {
Self { Self {
callID: call_id.clone(), callID: call_id.id(),
peerID: user_id.id(), peerID: user_id.id(),
} }
} }

View File

@ -1,54 +1,58 @@
//! # Conversation API object //! # Conversation API object
//! //!
//! @author Pierre Hubert //! @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::controllers::calls_controller;
use crate::data::conversation::Conversation; use crate::data::conversation::{Conversation, ConversationMember};
use crate::helpers::calls_helper; use crate::helpers::calls_helper;
/// Special implementation of conversation name (false if none / the name otherwise) #[derive(Serialize)]
struct ConvName(Option<String>); struct ConversationMembersAPI {
user_id: u64,
impl Serialize for ConvName { last_message_seen: u64,
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where following: bool,
S: Serializer { is_admin: bool,
match &self.0 {
None => serializer.serialize_bool(false),
Some(n) => serializer.serialize_str(n)
}
}
} }
#[derive(Serialize)] #[derive(Serialize)]
#[allow(non_snake_case)]
pub struct ConversationAPI { pub struct ConversationAPI {
ID: u64, id: u64,
ID_owner: u64, last_activity: u64,
last_active: u64, name: Option<String>,
name: ConvName, color: Option<String>,
following: LegacyBool, background: Option<String>,
saw_last_message: LegacyBool, group_id: Option<u64>,
members: Vec<u64>, members: Vec<ConversationMembersAPI>,
canEveryoneAddMembers: bool, can_everyone_add_members: bool,
can_have_call: bool, can_have_call: bool,
can_have_video_call: bool, can_have_video_call: bool,
has_call_now: 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 { impl ConversationAPI {
/// Construct a new Conversation instance /// Construct a new Conversation instance
pub fn new(conv: &Conversation) -> ConversationAPI { pub fn new(conv: &Conversation) -> ConversationAPI {
ConversationAPI { ConversationAPI {
ID: conv.id, id: conv.id.id(),
ID_owner: conv.owner_id.id(), last_activity: conv.last_activity,
last_active: conv.last_active, name: conv.name.clone(),
name: ConvName(conv.name.clone()), members: conv.members.iter().map(ConversationMembersAPI::new).collect(),
following: LegacyBool(conv.following), can_everyone_add_members: conv.can_everyone_add_members,
saw_last_message: LegacyBool(conv.saw_last_message), color: conv.color.clone(),
members: conv.members.iter().map(|x| x.id()).collect(), background: conv.background.clone(),
canEveryoneAddMembers: conv.can_everyone_add_members, group_id: conv.group_id.as_ref().map(|i| i.id()),
can_have_call: calls_helper::can_have_call(conv), can_have_call: calls_helper::can_have_call(conv),
can_have_video_call: calls_helper::can_have_video_calls(conv), can_have_video_call: calls_helper::can_have_video_calls(conv),

View File

@ -79,7 +79,7 @@ impl ConversationMessageAPI {
ConversationMessageAPI { ConversationMessageAPI {
id: msg.id, id: msg.id,
conv_id: msg.conv_id, conv_id: msg.conv_id.id(),
user_id: msg.user_id.clone().map(|u| u.id()), user_id: msg.user_id.clone().map(|u| u.id()),
time_insert: msg.time_sent, time_insert: msg.time_sent,
message: msg.message.clone(), message: msg.message.clone(),

View File

@ -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
}
}
}

View File

@ -18,7 +18,7 @@ pub struct JoinedCallMessage {
impl JoinedCallMessage { impl JoinedCallMessage {
pub fn new(call_id: &ConvID, user_id: &UserID) -> Self { pub fn new(call_id: &ConvID, user_id: &UserID) -> Self {
Self { Self {
callID: call_id.clone(), callID: call_id.id(),
userID: user_id.id(), userID: user_id.id(),
} }
} }

View File

@ -18,7 +18,7 @@ pub struct LeftCallMessage {
impl LeftCallMessage { impl LeftCallMessage {
pub fn new(call_id: &ConvID, user_id: &UserID) -> Self { pub fn new(call_id: &ConvID, user_id: &UserID) -> Self {
Self { Self {
callID: call_id.clone(), callID: call_id.id(),
userID: user_id.id(), userID: user_id.id(),
} }
} }

View File

@ -21,7 +21,6 @@ pub mod conversation_api;
mod legacy_api_bool; mod legacy_api_bool;
pub mod res_find_private_conversations; pub mod res_find_private_conversations;
pub mod conversation_message_api; pub mod conversation_message_api;
pub mod conversations_refresh_api;
pub mod res_count_unread_conversations; pub mod res_count_unread_conversations;
pub mod list_unread_conversations_api; pub mod list_unread_conversations_api;
pub mod global_search_result_api; pub mod global_search_result_api;

View File

@ -19,7 +19,7 @@ pub struct NewCallSignalAPI {
impl NewCallSignalAPI { impl NewCallSignalAPI {
pub fn new(call_id: &ConvID, peer_id: &UserID, data: &str) -> Res<Self> { pub fn new(call_id: &ConvID, peer_id: &UserID, data: &str) -> Res<Self> {
Ok(Self { Ok(Self {
callID: call_id.clone(), callID: call_id.id(),
peerID: peer_id.id(), peerID: peer_id.id(),
data: serde_json::from_str(data)?, data: serde_json::from_str(data)?,
}) })

View File

@ -3,6 +3,7 @@
//! @author Pierre Hubert //! @author Pierre Hubert
use serde::{Serialize}; use serde::{Serialize};
use crate::data::conversation::ConvID;
#[derive(Serialize)] #[derive(Serialize)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -13,9 +14,9 @@ pub struct ResCreateConversation {
impl ResCreateConversation { impl ResCreateConversation {
/// Construct a new Result instance /// Construct a new Result instance
pub fn new(conv_id: u64) -> ResCreateConversation { pub fn new(conv_id: ConvID) -> ResCreateConversation {
ResCreateConversation { ResCreateConversation {
conversationID: conv_id, conversationID: conv_id.id(),
success: "The conversation was successfully created!".to_string(), success: "The conversation was successfully created!".to_string(),
} }
} }

View File

@ -4,6 +4,8 @@
use serde::Serialize; use serde::Serialize;
use crate::data::conversation::ConvID;
#[derive(Serialize)] #[derive(Serialize)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct ResFindPrivateConversations { pub struct ResFindPrivateConversations {
@ -12,9 +14,9 @@ pub struct ResFindPrivateConversations {
impl ResFindPrivateConversations { impl ResFindPrivateConversations {
/// Construct a new instance of this structure /// Construct a new instance of this structure
pub fn new(list: Vec<u64>) -> ResFindPrivateConversations { pub fn new(list: Vec<ConvID>) -> ResFindPrivateConversations {
ResFindPrivateConversations { ResFindPrivateConversations {
conversationsID: list conversationsID: list.iter().map(|i| i.id()).collect()
} }
} }
} }

View File

@ -36,7 +36,6 @@ pub mod database_tables_names {
pub const CONV_LIST_TABLE: &str = "comunic_conversations_list"; pub const CONV_LIST_TABLE: &str = "comunic_conversations_list";
pub const CONV_MEMBERS_TABLE: &str = "comunic_conversations_members"; pub const CONV_MEMBERS_TABLE: &str = "comunic_conversations_members";
pub const CONV_MESSAGES_TABLE: &str = "comunic_conversations_messages"; pub const CONV_MESSAGES_TABLE: &str = "comunic_conversations_messages";
pub const CONV_INFO_VIEW: &str = "comunic_conversations_info";
/// Posts table /// Posts table
pub const POSTS_TABLE: &str = "texte"; pub const POSTS_TABLE: &str = "texte";
@ -164,3 +163,6 @@ pub const CLEAN_UP_INTERVAL: Duration = Duration::from_secs(60 * 60);
/// Minimal mobile version supported /// 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;

View File

@ -13,7 +13,6 @@ use crate::api_data::joined_call_message::JoinedCallMessage;
use crate::api_data::left_call_message::LeftCallMessage; use crate::api_data::left_call_message::LeftCallMessage;
use crate::api_data::new_call_signal::NewCallSignalAPI; use crate::api_data::new_call_signal::NewCallSignalAPI;
use crate::api_data::user_calls_config::UserCallsConfig; use crate::api_data::user_calls_config::UserCallsConfig;
use crate::routes::RequestResult;
use crate::controllers::user_ws_controller; use crate::controllers::user_ws_controller;
use crate::data::base_request_handler::BaseRequestHandler; use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::call_signal::{CallSignal, CloseCallStream, IceCandidate, NewUserCallSignal, SdpType, UserCallOfferRequest}; 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::data::user_ws_request_handler::UserWsRequestHandler;
use crate::helpers::{calls_helper, conversations_helper, events_helper}; use crate::helpers::{calls_helper, conversations_helper, events_helper};
use crate::helpers::events_helper::Event; use crate::helpers::events_helper::Event;
use crate::routes::RequestResult;
impl UserWsRequestHandler { impl UserWsRequestHandler {
/// Get the ID of a call included in a WebSocket request /// Get the ID of a call included in a WebSocket request
fn post_call_id(&mut self, name: &str) -> Res<ConvID> { 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) { if !self.get_conn().is_having_call_with_conversation(&conv_id) {
self.forbidden("You do not belong to this call!".to_string())?; 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 { 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 found
@ -133,10 +133,10 @@ pub fn is_conversation_having_call(conv_id: &ConvID) -> bool {
/// Join a call /// Join a call
pub fn join_call(r: &mut UserWsRequestHandler) -> RequestResult { 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 // 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) { if !calls_helper::can_have_call(&conv) {
r.forbidden("This conversation can not be used to make calls!".to_string())?; 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 // Warning ! For some technical reasons, we do not check if the user
// really belongs to the conversation, so be careful when manipulating // really belongs to the conversation, so be careful when manipulating
// conversation ID here // 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 // Check if the user was not in the conversation
if !r.get_conn().is_having_call_with_conversation(&conv_id) { 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 /// Get the hash associated to a call
pub fn gen_call_hash(call_id: &ConvID, peer_id: &UserID) -> String { 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 /// Handles client signal
@ -357,7 +357,7 @@ pub fn make_user_leave_call(conv_id: &ConvID, connection: &UserWsConnection) ->
// Notify user (if possible) // Notify user (if possible)
if connection.session.connected() { 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) // Close main stream (sender)
@ -418,7 +418,7 @@ pub fn handle_event(e: &events_helper::Event) -> Res {
return Ok(()); 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 peer_id = UserID::new(split[1].parse::<u64>()?);
let target_user = UserID::new(msg.peer_id.parse::<u64>()?); let target_user = UserID::new(msg.peer_id.parse::<u64>()?);

View File

@ -2,18 +2,18 @@
//! //!
//! @author Pierre Hubert //! @author Pierre Hubert
use std::collections::HashMap;
use crate::api_data::conversation_api::ConversationAPI; use crate::api_data::conversation_api::ConversationAPI;
use crate::api_data::conversation_message_api::ConversationMessageAPI; 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::list_unread_conversations_api::UnreadConversationAPI;
use crate::api_data::res_count_unread_conversations::ResultCountUnreadConversations; use crate::api_data::res_count_unread_conversations::ResultCountUnreadConversations;
use crate::api_data::res_create_conversation::ResCreateConversation; use crate::api_data::res_create_conversation::ResCreateConversation;
use crate::api_data::res_find_private_conversations::ResFindPrivateConversations; 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::controllers::user_ws_controller;
use crate::data::base_request_handler::BaseRequestHandler; 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::error::Res;
use crate::data::http_request_handler::HttpRequestHandler; use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::new_conversation::NewConversation; 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::data::user_ws_message::UserWsMessage;
use crate::helpers::{conversations_helper, events_helper, user_helper}; use crate::helpers::{conversations_helper, events_helper, user_helper};
use crate::helpers::events_helper::Event; use crate::helpers::events_helper::Event;
use crate::routes::RequestResult;
use crate::utils::string_utils::remove_html_nodes; use crate::utils::string_utils::remove_html_nodes;
/// Create a new conversation /// Create a new conversation
@ -58,6 +59,9 @@ pub fn create(r: &mut HttpRequestHandler) -> RequestResult {
owner_following: r.post_bool("follow")?, owner_following: r.post_bool("follow")?,
members, members,
can_everyone_add_members: r.post_bool_opt("canEveryoneAddMembers", true), 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 // Create the conversation
@ -74,77 +78,94 @@ pub fn get_list(r: &mut HttpRequestHandler) -> RequestResult {
/// Get information about a single conversation /// Get information about a single conversation
pub fn get_single(r: &mut HttpRequestHandler) -> RequestResult { pub fn get_single(r: &mut HttpRequestHandler) -> RequestResult {
let conversation_id = r.post_conv_id("conversationID")?; let conv = r.post_conv("conversationID")?;
let conv = conversations_helper::get_single(conversation_id, &r.user_id()?)?; let conv = conversations_helper::get_single(conv.conv_id)?;
r.set_response(ConversationAPI::new(&conv)) r.set_response(ConversationAPI::new(&conv))
} }
/// Update the settings of a conversation /// Update the settings of a conversation
pub fn update_settings(r: &mut HttpRequestHandler) -> RequestResult { pub fn update_settings(r: &mut HttpRequestHandler) -> RequestResult {
let conv_id = r.post_conv_id("conversationID")?; let conv_membership = r.post_conv("conversationID")?;
let is_moderator = conversations_helper::is_user_moderator(&r.user_id()?, conv_id)?; let conv = conversations_helper::get_single(conv_membership.conv_id)?;
// Update following state, if required // Update following state, if required
if r.has_post_parameter("following") { if r.has_post_parameter("following") {
conversations_helper::set_following( conversations_helper::set_following(
&r.user_id()?, &r.user_id()?,
conv_id, conv_membership.member_id,
r.post_bool("following")?, r.post_bool("following")?,
)?; )?;
} }
// Update members list // Update members list
if r.has_post_parameter("members") { if r.has_post_parameter("members") && !conv.is_managed() {
let mut members = r.post_numbers_list("members", 1)? let mut members = r.post_users_id("members")?
.iter() .into_iter()
.map(|f| UserID::new(f.clone())) .map(|user_id| ConversationMemberSetting { user_id, set_admin: false })
.collect::<Vec<UserID>>(); .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())?; r.forbidden("You can not update the list of members of this conversation!".to_string())?;
} }
// Check if the members of the conversation really exists // Set same admin status as earlier
for member in &members { if !conv_membership.is_admin {
if !user_helper::exists(member)? { members = members
r.not_found(format!("User {} not found!", member.id()))?; .into_iter()
} .map(|mut n| {
n.set_admin = conv.is_admin(&n.user_id);
n
})
.collect()
} }
if !members.contains(&r.user_id()?) { // If the user is an admin, use the values he gave
members.push(r.user_id()?); 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 // Change moderator settings
if r.has_post_parameter("name") || r.has_post_parameter("canEveryoneAddMembers") { if r.has_post_parameter("name")
if !is_moderator { && 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())?; r.forbidden("You are not allowed to perform changes on this conversation!".to_string())?;
} }
// Change conversation name
if r.has_post_parameter("name") { let new_settings = NewConversationSettings {
let name = r.post_string_opt("name", 0, true)?; conv_id: conv_membership.conv_id,
let name = if name.eq("false") || name.is_empty() { name: r.post_string_optional("name").map(|s| remove_html_nodes(&s)),
None color: r.post_color_opt("color")?,
} else { can_everyone_add_members: r.post_bool("canEveryoneAddMembers")?,
Some(remove_html_nodes(&name))
}; };
conversations_helper::set_name(conv_id, name)?; conversations_helper::set_settings(new_settings)?;
}
// Change "canEveryoneAddMembers" parameter
if r.has_post_parameter("canEveryoneAddMembers") {
conversations_helper::set_can_everyone_add_members(
conv_id, r.post_bool("canEveryoneAddMembers")?)?;
}
} }
r.success("Conversation information successfully updated!") r.success("Conversation information successfully updated!")
@ -169,6 +190,9 @@ pub fn find_private(r: &mut HttpRequestHandler) -> RequestResult {
owner_following: true, owner_following: true,
members: vec![r.user_id()?, other_user], members: vec![r.user_id()?, other_user],
can_everyone_add_members: true, can_everyone_add_members: true,
color: r.post_color_opt("color")?,
background: None,
group_id: None,
}; };
let conv_id = conversations_helper::create(&new_conv)?; let conv_id = conversations_helper::create(&new_conv)?;
list.push(conv_id); list.push(conv_id);
@ -177,80 +201,25 @@ pub fn find_private(r: &mut HttpRequestHandler) -> RequestResult {
r.set_response(ResFindPrivateConversations::new(list)) 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 /// Refresh a single conversation
pub fn refresh_single(r: &mut HttpRequestHandler) -> RequestResult { 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 last_message_id = r.post_u64("last_message_id")?;
let messages = match last_message_id { let messages = match last_message_id {
// Get latest messages of the conversation // 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 // 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)) r.set_response(ConversationMessageAPI::for_list(&messages))
} }
/// Get older messages of a conversation /// Get older messages of a conversation
pub fn get_older_messages(r: &mut HttpRequestHandler) -> RequestResult { 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; let max_id = r.post_u64("oldest_message_id")? - 1;
// Determine limit // Determine limit
@ -261,31 +230,44 @@ pub fn get_older_messages(r: &mut HttpRequestHandler) -> RequestResult {
_ => 60 _ => 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)) r.set_response(ConversationMessageAPI::for_list(&messages))
} }
/// Send a new message /// Send a new message
pub fn send_message(r: &mut HttpRequestHandler) -> RequestResult { pub fn send_message(r: &mut HttpRequestHandler) -> RequestResult {
let conv_id = r.post_conv_id("conversationID")?; let conv = r.post_conv("conversationID")?;
let message = r.post_string_without_html("message", 0, false)?;
// TODO : add support for other files type
// Get image // Get image
let image_path = match r.has_file("image") { let file = match r.has_file("image") {
false => None, 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 { let message = if let None = file {
r.bad_request("Message is empty!".to_string())?; Some(r.post_string_without_html("message", MIN_CONVERSATION_MESSAGE_LENGTH, true)?)
} } else {
None
};
conversations_helper::send_message(&NewConversationMessage { conversations_helper::send_message(&NewConversationMessage {
user_id: r.user_id()?, user_id: Some(r.user_id()?),
conv_id, conv_id: conv.conv_id,
message, message,
image_path, file,
server_message: None,
})?; })?;
r.success("Conversation message was successfully sent!") r.success("Conversation message was successfully sent!")
@ -307,9 +289,14 @@ pub fn list_unread(r: &mut HttpRequestHandler) -> RequestResult {
/// Delete a conversation /// Delete a conversation
pub fn delete_conversation(r: &mut HttpRequestHandler) -> RequestResult { 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") 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( user_ws_controller::send_message_to_specific_connections(
|f| f.conversations.contains(&msg.conv_id), |f| f.conversations.contains(&msg.conv_id),
|_| UserWsMessage::no_id_message("new_conv_message", ConversationMessageAPI::new(msg)), |_| 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)),
)?; )?;
} }

View File

@ -6,6 +6,7 @@ use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::Res; use crate::data::error::Res;
use crate::data::post::PostAccessLevel; use crate::data::post::PostAccessLevel;
use crate::data::user_ws_request_handler::UserWsRequestHandler; use crate::data::user_ws_request_handler::UserWsRequestHandler;
use crate::data::conversation::ConvID;
/// Update incognito status of the connection /// Update incognito status of the connection
pub fn set_incognito(r: &mut UserWsRequestHandler) -> Res { pub fn set_incognito(r: &mut UserWsRequestHandler) -> Res {
@ -17,14 +18,14 @@ pub fn set_incognito(r: &mut UserWsRequestHandler) -> Res {
/// Register a conversation /// Register a conversation
pub fn register_conv(r: &mut UserWsRequestHandler) -> Res { 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.update_conn(|c| { c.conversations.insert(conv_id); })?;
r.success("ok") r.success("ok")
} }
/// Un-register a conversation /// Un-register a conversation
pub fn unregister_conv(r: &mut UserWsRequestHandler) -> Res { 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.update_conn(|c| { c.conversations.remove(&conv_id); })?;
r.success("ok") r.success("ok")
} }

View File

@ -5,7 +5,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::data::comment::Comment; 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::conversation_message::ConversationMessage;
use crate::data::error::ResultBoxError; use crate::data::error::ResultBoxError;
use crate::data::friend::Friend; use crate::data::friend::Friend;
@ -24,7 +24,7 @@ pub struct AccountExport {
pub survey_responses: Vec<SurveyResponse>, pub survey_responses: Vec<SurveyResponse>,
pub all_conversation_messages: Vec<ConversationMessage>, pub all_conversation_messages: Vec<ConversationMessage>,
pub conversations: Vec<Conversation>, pub conversations: Vec<Conversation>,
pub conversation_messages: HashMap<u64, Vec<ConversationMessage>>, pub conversation_messages: HashMap<ConvID, Vec<ConversationMessage>>,
pub friends_list: Vec<Friend>, pub friends_list: Vec<Friend>,
pub groups: Vec<GroupID>, pub groups: Vec<GroupID>,
} }
@ -56,7 +56,7 @@ impl AccountExport {
// Conversation members // Conversation members
for conv in &self.conversations { 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 // Conversation messages

View File

@ -12,7 +12,7 @@ use serde::Serialize;
use crate::api_data::http_error::HttpError; use crate::api_data::http_error::HttpError;
use crate::constants::PASSWORD_MIN_LENGTH; use crate::constants::PASSWORD_MIN_LENGTH;
use crate::data::comment::Comment; 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::custom_emoji::CustomEmoji;
use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::error::{ExecError, Res, ResultBoxError};
use crate::data::group::GroupAccessLevel; 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::helpers::virtual_directory_helper::VirtualDirType;
use crate::routes::RequestResult; use crate::routes::RequestResult;
use crate::utils::pdf_utils::is_valid_pdf; 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::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path};
use crate::utils::virtual_directories_utils; use crate::utils::virtual_directories_utils;
use std::collections::HashSet;
#[derive(Serialize)] #[derive(Serialize)]
struct SuccessMessage { 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 /// Check out whether a file was included in the request or not
fn has_file(&self, name: &str) -> bool { fn has_file(&self, name: &str) -> bool {
self.post_parameter_opt(name) self.post_parameter_opt(name)
@ -420,6 +433,22 @@ pub trait BaseRequestHandler {
Ok(user_id) 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 /// 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 /// *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 /// Get & return the ID of the conversation included in the POST request
fn post_conv_id(&mut self, name: &str) -> ResultBoxError<ConvID> { fn post_conv(&mut self, name: &str) -> ResultBoxError<ConversationMember> {
let conv_id = self.post_u64(name)?; 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)? { Ok(membership)
self.forbidden(format!("You do not belong to conversation {} !", conv_id))?;
}
Ok(conv_id)
} }
/// Get the ID /// Get the ID
@ -624,4 +653,20 @@ pub trait BaseRequestHandler {
Ok(info) 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))
}
}
}
} }

View File

@ -2,20 +2,94 @@
//! //!
//! @author Pierre Hubert //! @author Pierre Hubert
use crate::data::group_id::GroupID;
use crate::data::user::UserID; 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 struct Conversation {
pub id: ConvID, pub id: ConvID,
pub owner_id: UserID,
pub name: Option<String>, 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_activity: u64,
pub members: Vec<ConversationMember>,
}
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, pub can_everyone_add_members: bool,
pub last_active: u64,
pub time_create: u64,
pub following: bool,
pub saw_last_message: bool,
} }

View File

@ -6,6 +6,7 @@ use std::collections::HashSet;
use crate::data::error::{ExecError, Res}; use crate::data::error::{ExecError, Res};
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::conversation::ConvID;
pub type ConvMessageID = u64; pub type ConvMessageID = u64;
@ -101,7 +102,7 @@ impl ConversationServerMessageType {
pub struct ConversationMessage { pub struct ConversationMessage {
pub id: ConvMessageID, pub id: ConvMessageID,
pub time_sent: u64, pub time_sent: u64,
pub conv_id: u64, pub conv_id: ConvID,
pub user_id: Option<UserID>, pub user_id: Option<UserID>,
pub message: Option<String>, pub message: Option<String>,
pub server_message: Option<ConversationServerMessageType>, pub server_message: Option<ConversationServerMessageType>,

View File

@ -3,11 +3,15 @@
//! @author Pierre Huber //! @author Pierre Huber
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::group_id::GroupID;
#[derive(Debug)] #[derive(Debug)]
pub struct NewConversation { pub struct NewConversation {
pub owner_id: UserID, pub owner_id: UserID,
pub name: Option<String>, pub name: Option<String>,
pub group_id: Option<GroupID>,
pub color: Option<String>,
pub background: Option<String>,
pub owner_following: bool, pub owner_following: bool,
pub members: Vec<UserID>, pub members: Vec<UserID>,
pub can_everyone_add_members: bool pub can_everyone_add_members: bool

View File

@ -3,11 +3,14 @@
//! @author Pierre Hubert //! @author Pierre Hubert
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::conversation::ConvID;
use crate::data::conversation_message::{ConversationMessageFile, ConversationServerMessageType};
/// Information about a new conversation message /// Information about a new conversation message
pub struct NewConversationMessage { pub struct NewConversationMessage {
pub user_id: UserID, pub user_id: Option<UserID>,
pub conv_id: u64, pub conv_id: ConvID,
pub message: String, pub message: Option<String>,
pub image_path: Option<String> pub file: Option<ConversationMessageFile>,
pub server_message: Option<ConversationServerMessageType>
} }

View File

@ -20,7 +20,7 @@ impl UserMembership {
match self { match self {
UserMembership::Group(_, last_active) => *last_active, UserMembership::Group(_, last_active) => *last_active,
UserMembership::Friend(f) => f.last_activity_time, UserMembership::Friend(f) => f.last_activity_time,
UserMembership::Conversation(c) => c.last_active, UserMembership::Conversation(c) => c.last_activity,
} }
} }
} }

View File

@ -3,7 +3,7 @@
//! @author Pierre Hubert //! @author Pierre Hubert
use crate::constants::database_tables_names::{CONV_LIST_TABLE, CONV_MEMBERS_TABLE, CONV_MESSAGES_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::{Conversation, ConversationMember, ConversationMemberSetting, ConvID, NewConversationSettings};
use crate::data::conversation_message::{ConversationMessage, ConversationMessageFile, ConversationServerMessageType}; use crate::data::conversation_message::{ConversationMessage, ConversationMessageFile, ConversationServerMessageType};
use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::error::{ExecError, Res, ResultBoxError};
use crate::data::new_conversation::NewConversation; 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::unread_conversation::UnreadConversation;
use crate::data::user::{User, UserID}; use crate::data::user::{User, UserID};
use crate::helpers::{database, events_helper}; 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::helpers::events_helper::Event;
use crate::utils::date_utils::time; use crate::utils::date_utils::time;
use crate::utils::user_data_utils::delete_user_data_file_if_exists; 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 /// 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 // Create the conversation in the main table
let conv_id = InsertQuery::new(CONV_LIST_TABLE) let conv_id = InsertQuery::new(CONV_LIST_TABLE)
.add_user_id("user_id", &conv.owner_id) .add_user_id("user_id", &conv.owner_id)
.add_str("name", conv.name.clone().unwrap_or(String::new()).as_str()) .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_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) .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 // Add the members to the conversation
for member in &conv.members { for member in &conv.members {
// Check following status of the member // Check following status of the member
let mut follow = true; let mut follow = true;
let mut admin = false;
if member.eq(&conv.owner_id) { if member.eq(&conv.owner_id) {
follow = conv.owner_following; follow = conv.owner_following;
admin = true;
} }
add_member(conv_id, member, follow)?; add_member(conv_id, member, follow, admin)?;
} }
Ok(conv_id) Ok(conv_id)
} }
/// Add a member to a conversation /// 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) 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_user_id("user_id", user_id)
.add_u64("time_add", time()) .add_u64("added_on", time())
.add_legacy_bool("following", following) .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()?; .insert()?;
// TODO : create a message
Ok(()) Ok(())
} }
/// Remove a member from a conversation /// Update admin status of a member for a conversation
pub fn remove_member(conv_id: u64, user_id: &UserID) -> ResultBoxError<()> { pub fn set_admin(conv_id: &ConvID, user_id: &UserID, admin: bool) -> Res {
database::DeleteQuery::new(CONV_MEMBERS_TABLE) UpdateInfo::new(CONV_MEMBERS_TABLE)
.cond_u64("conv_id", conv_id)
.cond_user_id("user_id", user_id) .cond_user_id("user_id", user_id)
.cond_conv_id("conv_id", conv_id.clone())
.set_legacy_bool("is_admin", admin)
.exec() .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 /// Get the list of conversations of a specific user
pub fn get_list_user(user_id: &UserID) -> ResultBoxError<Vec<Conversation>> { pub fn get_list_user(user_id: &UserID) -> ResultBoxError<Vec<Conversation>> {
database::QueryInfo::new(CONV_LIST_TABLE) 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") .join(CONV_MEMBERS_TABLE, "u", "l.id = u.conv_id")
// Specify selected fields // Specify selected fields
.add_field("*") .add_field("l.*")
.add_field("l.id as id")
.add_field("l.user_id as owner_id")
// Filter query // Filter query
.cond_user_id("u.user_id", user_id) .cond_user_id("u.user_id", user_id)
// Sort results // Sort results
.set_order("l.last_active DESC") .set_order("l.last_activity DESC")
// Execute query // Execute query
.exec(db_to_conversation_info) .exec(db_to_conversation_info)
} }
/// Get information about a single conversation /// 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 // Tables
database::QueryInfo::new(CONV_LIST_TABLE) database::QueryInfo::new(CONV_LIST_TABLE)
.alias("l") .cond_conv_id("id", conv_id)
.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)
.query_row(db_to_conversation_info) .query_row(db_to_conversation_info)
} }
/// Get the list of members of a conversation /// 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) database::QueryInfo::new(CONV_MEMBERS_TABLE)
.cond_u64("conv_id", conv_id) .cond_conv_id("conv_id", conv_id)
.add_field("user_id") .exec(db_to_conversation_member)
.exec(|res| res.get_user_id("user_id"))
} }
/// Check if a user belongs to a conversation or not /// Check if a user belongs to a conversation or not
pub fn does_user_belongs_to(user_id: &UserID, conv_id: u64) -> ResultBoxError<bool> { pub fn get_user_membership(user_id: &UserID, conv_id: ConvID) -> Res<ConversationMember> {
Ok(database::QueryInfo::new(CONV_MEMBERS_TABLE) database::QueryInfo::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_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 /// 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) database::QueryInfo::new(CONV_LIST_TABLE)
.cond_u64("id", conv_id) .cond_conv_id("id", conv_id)
.add_field("can_everyone_add_members") .add_field("can_everyone_add_members")
.query_row(|f| f.get_legacy_bool("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 /// 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)?; let curr_list = get_list_members(conv_id)?;
// Add new members // Add new members
for member in new_list { for member in new_list {
if curr_list.contains(member) { if let Some(user) = curr_list.iter().filter(|m| m.user_id == member.user_id).next() {
continue; // 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 // Remove a member
if can_delete { if can_delete {
for member in curr_list { for member in curr_list {
if new_list.contains(&member) { if new_list.iter().any(|m| m.user_id.eq(&member.user_id)) {
continue; 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 /// 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) database::UpdateInfo::new(CONV_LIST_TABLE)
.cond_u64("id", conv_id) .cond_conv_id("id", settings.conv_id)
.set_opt_str("name", name) .set_opt_str("name", settings.name)
.exec() .set_opt_str("color", settings.color)
} .set_legacy_bool("can_everyone_add_members", settings.can_everyone_add_members)
/// 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)
.exec() .exec()
} }
/// Search for private conversation between two users /// 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) database::QueryInfo::new(CONV_MEMBERS_TABLE)
.alias("t1") .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) .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()) .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") .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 /// 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 /// 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) 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) .cond_user_id("user_id", user_id)
.exec(db_to_conversation_message) .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 /// Remove the user from all the conversations he belongs to
pub fn delete_all_user_conversations(user_id: &UserID) -> ResultBoxError { pub fn delete_all_user_conversations(user_id: &UserID) -> ResultBoxError {
for conversation in &get_list_user(user_id)? { 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(()) Ok(())
} }
/// Get the entire list of messages of a given conversation /// 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) database::QueryInfo::new(CONV_MESSAGES_TABLE)
.cond_u64("conv_id", conv_id) .cond_conv_id("conv_id", conv_id)
.exec(db_to_conversation_message) .exec(db_to_conversation_message)
} }
@ -318,39 +318,45 @@ pub fn send_message(msg: &NewConversationMessage) -> ResultBoxError<()> {
let t = time(); let t = time();
// Insert the message in the database // Insert the message in the database
let msg_id = database::InsertQuery::new(CONV_MESSAGES_TABLE) let mut msg_request = database::InsertQuery::new(CONV_MESSAGES_TABLE)
.add_u64("conv_id", msg.conv_id) .add_conv_id("conv_id", msg.conv_id)
.add_user_id("user_id", &msg.user_id) .add_u64("user_id", msg.user_id.as_ref().map(|u| u.id()).unwrap_or(0))
.add_u64("time_insert", t) .add_u64("time_sent", t);
.add_str("message", msg.message.as_str())
.add_opt_str("image_path", msg.image_path.as_ref()) if let Some(server_msg) = &msg.server_message {
.insert_expect_result()?; 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 // Update the last activity of the conversation
database::UpdateInfo::new(CONV_LIST_TABLE) database::UpdateInfo::new(CONV_LIST_TABLE)
.cond_u64("id", msg.conv_id) .cond_conv_id("id", msg.conv_id)
.set_u64("last_active", t) .set_u64("last_activity", t)
.exec()?; .exec()?;
// Get the list of users to notify after the update // Get the list of users to notify after the update
let list_to_notify = database::QueryInfo::new(CONV_MEMBERS_TABLE) let list_to_notify = database::QueryInfo::new(CONV_MEMBERS_TABLE)
.cond_u64("conv_id", msg.conv_id) .cond_conv_id("conv_id", msg.conv_id)
.cond_legacy_bool("saw_last_message", true)
.cond_legacy_bool("following", true) .cond_legacy_bool("following", true)
.set_custom_where("user_id != ?") .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"))?; .exec(|r| r.get_user_id("user_id"))?;
// Mark all the users of the conversation as unread // Mark the user has seen his message
database::UpdateInfo::new(CONV_MEMBERS_TABLE) if let Some(user_id) = &msg.user_id {
.cond_u64("conv_id", msg.conv_id) mark_user_seen(msg.conv_id, user_id, msg_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()?;
// Send an event (updated_number_unread_conversations) // Send an event (updated_number_unread_conversations)
events_helper::propagate_event(&Event::UpdatedNumberUnreadConversations(&list_to_notify))?; 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_user_id("users.user_id", user_id)
.cond_legacy_bool("users.following", true) .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("messages.conv_id")
.add_field("name") .add_field("name")
.add_field("last_active") .add_field("last_activity")
.add_field("messages.user_id") .add_field("messages.user_id")
.add_field("message") .add_field("message")
.exec(|res| Ok(UnreadConversation { .exec(|res| Ok(UnreadConversation {
id: res.get_u64("conv_id")?, id: res.get_u64("conv_id")?,
name: res.get_optional_str("name")?, 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")?, user_id: res.get_user_id("user_id")?,
message: res.get_str("message")?, message: res.get_str("message")?,
})) }))
} }
/// Indicate that a user has seen the last messages of a conversation /// 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) 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_user_id("user_id", user_id)
.cond_legacy_bool("saw_last_message", false) .set_u64("last_message_seen", last_msg)
.set_legacy_bool("saw_last_message", true)
.exec()?; .exec()?;
// Push an event (updated_number_unread_conversations) // 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 /// Remove a user from a conversation
pub fn remove_user_from_conversation(user_id: &UserID, conv_id: u64) -> ResultBoxError<()> { pub fn remove_user_from_conversation(user_id: &UserID, conv: &Conversation, remover: &UserID) -> ResultBoxError<()> {
if is_user_moderator(user_id, conv_id)? { if conv.is_last_admin(user_id) {
delete_conversation(conv_id) delete_conversation(conv)
} else { } else {
delete_member(user_id, conv_id) delete_member(user_id, conv.id, remover)
} }
} }
/// Remove permanently a conversation /// 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 // 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_message(&message)?;
} }
// Delete all the members of the conversation // Delete all the members of the conversation
database::DeleteQuery::new(CONV_MEMBERS_TABLE) database::DeleteQuery::new(CONV_MEMBERS_TABLE)
.cond_u64("conv_id", conv_id) .cond_conv_id("conv_id", conv.id)
.exec()?; .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 // Delete the conversation entry itself
database::DeleteQuery::new(CONV_LIST_TABLE) database::DeleteQuery::new(CONV_LIST_TABLE)
.cond_u64("id", conv_id) .cond_conv_id("id", conv.id)
.exec()?; .exec()?;
Ok(()) Ok(())
} }
/// Delete a conversation membership /// 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)? { for msg in get_user_messages_for_conversations(conv_id, user_id)? {
delete_message(&msg)?; 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 /// Turn a database entry into a ConversationInfo object
fn db_to_conversation_info(row: &database::RowResult) -> ResultBoxError<Conversation> { 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 { Ok(Conversation {
id: conv_id, 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")?, name: row.get_optional_str("name")?,
members: get_list_members(conv_id)?, members: get_list_members(conv_id)?,
can_everyone_add_members: row.get_legacy_bool("can_everyone_add_members")?, can_everyone_add_members: row.get_legacy_bool("can_everyone_add_members")?,
last_active: row.get_u64("last_active")?, last_activity: row.get_u64("last_activity")?,
time_create: row.get_u64("time_add")?, 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")?, 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")?) 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, true => None,
false => Some(ConversationMessageFile { false => Some(ConversationMessageFile {
path: row.get_str("filepath")?, path: row.get_str("file_path")?,
size: row.get_u64("file_size")?, size: row.get_u64("file_size")?,
name: row.get_str("file_name")?, name: row.get_str("file_name")?,
thumbnail: row.get_optional_str("file_thumbnail")?, thumbnail: row.get_optional_str("file_thumbnail")?,
@ -553,7 +575,7 @@ fn db_to_conversation_message(row: &database::RowResult) -> ResultBoxError<Conve
Ok(ConversationMessage { Ok(ConversationMessage {
id: row.get_u64("id")?, id: row.get_u64("id")?,
time_sent: row.get_u64("time_sent")?, time_sent: row.get_u64("time_sent")?,
conv_id: row.get_u64("conv_id")?, conv_id: row.get_conv_id("conv_id")?,
user_id, user_id,
message, message,
server_message, server_message,

View File

@ -12,6 +12,7 @@ use crate::data::config::{conf, DatabaseConfig};
use crate::data::error::{ExecError, ResultBoxError}; use crate::data::error::{ExecError, ResultBoxError};
use crate::data::group_id::GroupID; use crate::data::group_id::GroupID;
use crate::data::user::UserID; use crate::data::user::UserID;
use crate::data::conversation::ConvID;
/// Database access helper /// Database access helper
/// ///
@ -174,6 +175,11 @@ impl QueryInfo {
self 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 { pub fn cond_legacy_bool(mut self, key: &str, val: bool) -> QueryInfo {
let val = match val { let val = match val {
true => 1, true => 1,
@ -358,6 +364,19 @@ impl<'a> RowResult<'a> {
Ok(GroupID::new(self.get_u64(name)?)) 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 /// Find a string included in the request
pub fn get_str(&self, name: &str) -> Result<String, Box<dyn Error>> { pub fn get_str(&self, name: &str) -> Result<String, Box<dyn Error>> {
let value = self.row.get_opt(self.find_col(name)?); let value = self.row.get_opt(self.find_col(name)?);
@ -588,6 +607,19 @@ impl InsertQuery {
self 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 /// Add an integer
pub fn add_i64(mut self, key: &str, value: i64) -> InsertQuery { pub fn add_i64(mut self, key: &str, value: i64) -> InsertQuery {
self.values.insert(key.to_string(), Value::from(value)); self.values.insert(key.to_string(), Value::from(value));
@ -619,6 +651,12 @@ impl InsertQuery {
self 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) /// Legacy database boolean (1 = true / 0 = false)
pub fn add_legacy_bool(mut self, key: &str, value: bool) -> InsertQuery { pub fn add_legacy_bool(mut self, key: &str, value: bool) -> InsertQuery {
@ -758,6 +796,11 @@ impl DeleteQuery {
self 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 /// Execute the delete query
pub fn exec(self) -> ResultBoxError<()> { pub fn exec(self) -> ResultBoxError<()> {
delete(self) delete(self)
@ -832,6 +875,13 @@ impl UpdateInfo {
self 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 /// Filter with an unsigned integer
pub fn cond_u64(mut self, name: &str, val: u64) -> UpdateInfo { pub fn cond_u64(mut self, name: &str, val: u64) -> UpdateInfo {
self.cond.insert(name.to_string(), Value::UInt(val)); self.cond.insert(name.to_string(), Value::UInt(val));

View File

@ -196,7 +196,6 @@ pub fn get_routes() -> Vec<Route> {
Route::post("/conversations/getInfosOne", Box::new(conversations_controller::get_single)), Route::post("/conversations/getInfosOne", Box::new(conversations_controller::get_single)),
Route::post("/conversations/updateSettings", Box::new(conversations_controller::update_settings)), Route::post("/conversations/updateSettings", Box::new(conversations_controller::update_settings)),
Route::post("/conversations/getPrivate", Box::new(conversations_controller::find_private)), 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/refresh_single", Box::new(conversations_controller::refresh_single)),
Route::post("/conversations/get_older_messages", Box::new(conversations_controller::get_older_messages)), Route::post("/conversations/get_older_messages", Box::new(conversations_controller::get_older_messages)),
Route::post("/conversations/sendMessage", Box::new(conversations_controller::send_message)), 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/delete", Box::new(conversations_controller::delete_conversation)),
Route::post("/conversations/updateMessage", Box::new(conversations_controller::update_message)), Route::post("/conversations/updateMessage", Box::new(conversations_controller::update_message)),
Route::post("/conversations/deleteMessage", Box::new(conversations_controller::delete_message)), Route::post("/conversations/deleteMessage", Box::new(conversations_controller::delete_message)),
// TODO : add a route to mark messages seen
// Search controller // Search controller

View File

@ -93,3 +93,17 @@ pub fn check_emoji_code(shortcut: &str) -> bool {
let r = Regex::new(r"^:[a-zA-Z0-9]+:$").unwrap(); let r = Regex::new(r"^:[a-zA-Z0-9]+:$").unwrap();
r.is_match(shortcut) 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)
}