1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-01-06 10:58:50 +00:00
comunicapiv3/src/controllers/conversations_controller.rs

536 lines
18 KiB
Rust

//! # Conversations controller
//!
//! @author Pierre Hubert
use std::collections::HashSet;
use crate::api_data::conversation_api::ConversationAPI;
use crate::api_data::conversation_message_api::ConversationMessageAPI;
use crate::api_data::list_unread_conversations_api::UnreadConversationAPI;
use crate::api_data::remove_user_from_conv_message::RemovedUserFromConversationMessage;
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::constants::{ALLOWED_CONVERSATION_FILES_TYPES, CONVERSATION_FILES_MAX_SIZE, MAX_CONVERSATION_MESSAGE_LENGTH, MIN_CONVERSATION_MESSAGE_LENGTH};
use crate::controllers::user_ws_controller;
use crate::data::base_request_handler::{BaseRequestHandler, RequestValue};
use crate::data::conversation::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;
use crate::data::new_conversation_message::NewConversationMessage;
use crate::data::user_ws_connection::UserWsConnection;
use crate::data::user_ws_message::UserWsMessage;
use crate::helpers::{conversations_helper, events_helper};
use crate::helpers::events_helper::Event;
use crate::routes::RequestResult;
use crate::utils::string_utils::remove_html_nodes;
use crate::utils::user_data_utils::{delete_user_data_file_if_exists, user_data_path};
/// Create a new conversation
pub fn create(r: &mut HttpRequestHandler) -> RequestResult {
let name = r.post_string("name")?;
let mut members = r.post_users_id("users")?;
// Adapt name
let name = match name.as_str() {
"false" => None,
s => Some(s.to_string())
};
// Add current user ID if required
members.insert(r.user_id()?);
let conv = NewConversation {
owner_id: r.user_id()?,
name,
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,
logo: None,
};
// Create the conversation
let conv_id = conversations_helper::create(&conv)?;
r.set_response(ResCreateConversation::new(conv_id))
}
/// Get the list of conversations of a user
pub fn get_list(r: &mut HttpRequestHandler) -> RequestResult {
let list = conversations_helper::get_list_user(&r.user_id()?)?;
r.set_response(list.iter().map(|c| ConversationAPI::new(c)).collect::<Vec<ConversationAPI>>())
}
/// Get information about a single conversation
pub fn get_single(r: &mut HttpRequestHandler) -> RequestResult {
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_membership = r.post_conv("conversationID")?;
// Update following state, if required
if r.has_post_parameter("following") {
conversations_helper::set_following(
&r.user_id()?,
conv_membership.conv_id,
r.post_bool("following")?,
)?;
}
// Change moderator settings
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())?;
}
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")?,
};
conversations_helper::set_settings(new_settings)?;
}
r.success("Conversation information successfully updated!")
}
/// Change conversation image
pub fn change_image(r: &mut HttpRequestHandler) -> RequestResult {
let conv_membership = r.post_conv_admin("convID")?;
let conv = conversations_helper::get_single(conv_membership.conv_id)?;
let new_image = r.save_post_image("file", "conv-image", 200, 200)?;
conversations_helper::remove_conversation_image(&conv)?;
conversations_helper::set_conversation_image(&conv, &new_image)?;
r.ok()
}
/// Delete conversation image
pub fn delete_image(r: &mut HttpRequestHandler) -> RequestResult {
let conv_membership = r.post_conv_admin("convID")?;
let conv = conversations_helper::get_single(conv_membership.conv_id)?;
conversations_helper::remove_conversation_image(&conv)?;
r.ok()
}
/// Add a new member to a conversation
pub fn add_member(r: &mut HttpRequestHandler) -> RequestResult {
let conv_membership = r.post_conv("convID")?;
let conv = conversations_helper::get_single(conv_membership.conv_id)?;
let user_to_add = r.post_user_id("userID")?;
if conv.is_managed() {
r.bad_request("This conversation is managed, you can not manually change its members!".to_string())?;
}
if !conv.can_user_add_members(r.user_id_ref()?) {
r.forbidden("You are not allowed to add members to this conversation!".to_string())?;
}
if conv.get_membership(&user_to_add).is_some() {
r.bad_request("This user is already a member of this conversation!".to_string())?;
}
conversations_helper::add_member(conv.id, &user_to_add, true, false, r.user_id_ref()?)?;
r.success("The user was added to the conversation!")
}
/// Update admin status of a user
pub fn set_admin(r: &mut HttpRequestHandler) -> RequestResult {
let conv_membership = r.post_conv_admin("convID")?;
let conv = conversations_helper::get_single(conv_membership.conv_id)?;
let user_to_update = r.post_user_id("userID")?;
let set_admin = r.post_bool("setAdmin")?;
if conv.is_managed() {
r.bad_request("This conversation is managed, you can not manually change its members!".to_string())?;
}
if !conv.can_mark_other_users_admin(r.user_id_ref()?) {
r.forbidden("You are not allowed to make users admin in this conversation!".to_string())?;
}
if conv.get_membership(&user_to_update).is_none() {
r.bad_request("This user is not a member of this conversation!".to_string())?;
}
conversations_helper::set_admin(&conv.id, &user_to_update, set_admin)?;
r.success("The user was added to the conversation!")
}
/// Remove a member from a conversation
pub fn remove_member(r: &mut HttpRequestHandler) -> RequestResult {
let conv_membership = r.post_conv_admin("convID")?;
let conv = conversations_helper::get_single(conv_membership.conv_id)?;
let user_to_remove = r.post_user_id("userID")?;
if conv.is_managed() {
r.bad_request("This conversation is managed, you can not manually change its members!".to_string())?;
}
if !conv.can_user_remove_members(r.user_id_ref()?) {
r.forbidden("You are not allowed to remove members from this conversation!".to_string())?;
}
if conv.get_membership(&user_to_remove).is_none() {
r.bad_request("This user is not a member of this conversation!".to_string())?;
}
conversations_helper::remove_member(&user_to_remove, conv.id, r.user_id_ref()?)?;
r.ok()
}
/// Find a private conversation
pub fn find_private(r: &mut HttpRequestHandler) -> RequestResult {
let other_user = r.post_user_id("otherUser")?;
let allow_create = r.post_bool_opt("allowCreate", false);
// Query the database
let mut list = conversations_helper::find_private(&r.user_id()?, &other_user)?;
if list.is_empty() {
if !allow_create {
return r.not_found(format!("Not any private conversation was found. The server was not allowed to create a new one..."));
}
let mut members = HashSet::new();
members.insert(r.user_id()?);
members.insert(r.user_id()?);
let new_conv = NewConversation {
owner_id: r.user_id()?,
name: None,
owner_following: true,
members,
can_everyone_add_members: true,
color: None,
logo: None,
group_id: None,
};
let conv_id = conversations_helper::create(&new_conv)?;
list.push(conv_id);
}
r.set_response(ResFindPrivateConversations::new(list))
}
/// Refresh a single conversation
pub fn refresh_single(r: &mut HttpRequestHandler) -> RequestResult {
let conv = r.post_conv("conversationID")?;
let last_message_id = r.post_u64("last_message_id")?;
let mut messages = match last_message_id {
// Get latest messages of the conversation
0 => conversations_helper::get_last_messages(conv.conv_id, 10)?,
// Get new messages
_ => conversations_helper::get_new_messages(conv.conv_id, last_message_id)?,
};
messages.sort_by(|one, two| one.id.cmp(&two.id));
if messages.len() > 0 && messages.last().unwrap().id > conv.last_message_seen {
conversations_helper::mark_user_seen(
conv.conv_id,
r.user_id_ref()?,
&messages.last().unwrap(),
)?;
}
r.set_response(ConversationMessageAPI::for_list(&messages))
}
/// Get older messages of a conversation
pub fn get_older_messages(r: &mut HttpRequestHandler) -> RequestResult {
let conv = r.post_conv("conversationID")?;
let max_id = r.post_u64("oldest_message_id")? - 1;
// Determine limit
let limit = r.post_u64("limit")?;
let limit = match limit {
0 => 1,
1..=60 => limit,
_ => 60
};
let messages = conversations_helper::get_older_messages(conv.conv_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 = r.post_conv("conversationID")?;
// Get associated file
let file = match r.post_parameter_opt("file") {
Some(RequestValue::File(file)) => {
// File name
let mut name = file.name.to_string();
if file.buff.len() > CONVERSATION_FILES_MAX_SIZE {
r.bad_request("File is too big!".to_string())?;
}
// Determine file mime type
let mut mime_type = r.post_file_type("file")?;
// Check for thumbnail
let mut thumbnail = match r.has_file("thumbnail") {
false => None,
true => Some("thumbnail".to_string())
};
let path;
if !ALLOWED_CONVERSATION_FILES_TYPES.contains(&mime_type.as_str()) {
r.bad_request("File type is not allowed!".to_string())?;
}
// Images
if mime_type.starts_with("image/") {
if let None = thumbnail {
thumbnail = Some("file".to_string());
}
path = r.save_post_image("file", "conversation", 2000, 2000)?;
mime_type = "image/png".to_string();
name = "picture.png".to_string();
}
// PDF
else if mime_type.eq("application/pdf") {
path = r.save_post_pdf("file", "conversation")?;
}
// MP3
else if mime_type.eq("audio/mpeg") {
path = r.save_post_mp3("file", "conversation")?;
}
// MP4
else if mime_type.eq("video/mp4") {
path = r.save_post_mp4("file", "conversation")?;
}
// ZIP archive
else if mime_type.eq("application/zip") {
path = r.save_post_zip("file", "conversation")?;
}
// Office document
else if mime_type.starts_with("application/") {
path = r.save_post_office_doc("file", "conversation")?;
}
// Text files
else {
path = r.save_post_txt_doc("file", "conversation")?;
}
// Attempt to save thumbnail, if it fails we can not save message
let thumbnail = match thumbnail {
None => None,
Some(f) => Some(match r.save_post_image(&f, "conversations-thumb", 200, 200) {
Ok(s) => Ok(s),
Err(e) => {
eprintln!("Failed to save conversation thumbnail! {:#?}", e);
delete_user_data_file_if_exists(&path).unwrap();
Err(e)
}
}?)
};
Some(ConversationMessageFile {
path: path.clone(),
size: std::fs::metadata(user_data_path(path.as_ref()))?.len(),
name,
thumbnail,
r#type: mime_type,
})
}
_ => None,
};
// Get message, if there is no file
let message = if let None = file {
let msg = r.post_string_without_html("message", MIN_CONVERSATION_MESSAGE_LENGTH, true)?;
if msg.len() > MAX_CONVERSATION_MESSAGE_LENGTH {
r.bad_request("Message is too long!".to_string())?;
}
Some(msg)
} else {
None
};
conversations_helper::send_message(&NewConversationMessage {
user_id: Some(r.user_id()?),
conv_id: conv.conv_id,
message,
file,
server_message: None,
})?;
r.success("Conversation message was successfully sent!")
}
/// Count the number of unread conversation of the user
pub fn count_unread(r: &mut HttpRequestHandler) -> RequestResult {
let num = conversations_helper::count_unread_for_user(&r.user_id()?)?;
r.set_response(ResultCountUnreadConversations::new(num))
}
/// Get the list of unread conversations of a user
pub fn list_unread(r: &mut HttpRequestHandler) -> RequestResult {
let list = conversations_helper::get_list_unread(&r.user_id()?)?;
r.set_response(UnreadConversationAPI::for_list(&list)?)
}
/// Delete a conversation
pub fn delete_conversation(r: &mut HttpRequestHandler) -> RequestResult {
let conv_membership = r.post_conv("conversationID")?;
let conv = conversations_helper::get_single(conv_membership.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")
}
/// Update a single conversation message
pub fn update_message(r: &mut HttpRequestHandler) -> RequestResult {
let msg_id = r.post_u64("messageID")?;
let new_content = r.post_string_opt("content", MIN_CONVERSATION_MESSAGE_LENGTH, true)?;
let msg = conversations_helper::get_single_message(msg_id)?;
if msg.user_id != r.user_id_opt() {
r.forbidden("You are not the owner of this message!".to_string())?;
}
if msg.file.is_some() {
r.bad_request("Can not have both text and file in the same message!".to_string())?;
}
if new_content.len() > MAX_CONVERSATION_MESSAGE_LENGTH {
r.bad_request("New message is too long!".to_string())?;
}
conversations_helper::update_message_content(msg_id, &new_content)?;
r.success("Conversation message content successfully updated")
}
/// Delete a conversation message
pub fn delete_message(r: &mut HttpRequestHandler) -> RequestResult {
let msg_id = r.post_u64("messageID")?;
if !conversations_helper::is_message_owner(&r.user_id()?, msg_id)? {
r.forbidden("You are not the owner of this message!".to_string())?;
}
conversations_helper::delete_message_by_id(msg_id)?;
r.success("The message has been successfully deleted!")
}
/// Events handler
pub fn handle_event(e: &events_helper::Event) -> Res {
match e {
Event::UpdatedNumberUnreadConversations(users) => {
for user in users.iter() {
if user_ws_controller::is_user_connected(user) {
user_ws_controller::send_message_to_user(
&UserWsMessage::no_id_message(
"number_unread_conversations",
conversations_helper::count_unread_for_user(user)?,
)?,
user,
)?;
}
}
}
Event::NewConversationMessage(msg) => {
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(), msg)),
)?;
}
Event::UpdatedConversationMessage(msg) => {
user_ws_controller::send_message_to_specific_connections(
|f| f.conversations.contains(&msg.conv_id),
|_| UserWsMessage::no_id_message("updated_conv_message", ConversationMessageAPI::new(msg)),
None::<fn(&_) -> _>,
)?;
}
Event::DeleteConversationMessage(msg) => {
user_ws_controller::send_message_to_specific_connections(
|f| f.conversations.contains(&msg.conv_id),
|_| UserWsMessage::no_id_message("deleted_conv_message", ConversationMessageAPI::new(msg)),
None::<fn(&_) -> _>,
)?;
}
Event::RemovedUserFromConversation(user_id, conv_id) => {
// Notify users
user_ws_controller::send_message_to_specific_connections(
|f| f.conversations.contains(conv_id),
|_| UserWsMessage::no_id_message("removed_user_from_conv", RemovedUserFromConversationMessage::new(user_id, *conv_id)),
None::<fn(&_) -> _>,
)?;
// Disconnect users from conversation
user_ws_controller::foreach_connection(|f| {
if f.user_id() == user_id && f.conversations.contains(conv_id) {
f.clone().replace(|w| {
w.conversations.remove(conv_id);
});
}
Ok(())
})?;
}
_ => {}
}
Ok(())
}