//! # Notifications helper //! //! @author Pierre Hubert use std::collections::HashMap; use crate::constants::database_tables_names::NOTIFICATIONS_TABLE; use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::group_id::GroupID; use crate::data::notification::{NotifElemType, NotifEventType, NotifEventVisibility, Notification, PartialNotification}; use crate::data::post::{PostID, PostPageKind, PostVisibilityLevel}; use crate::data::user::{User, UserID}; use crate::helpers::{database, events_helper, groups_helper, posts_helper}; use crate::helpers::events_helper::Event; use crate::helpers::friends_helper::GetFriendsQuery; use crate::utils::date_utils; use crate::utils::date_utils::time; /// Create post notification pub fn create_post_notification(from_user: &UserID, post_id: u64, action: NotifEventType) -> ResultBoxError { let mut n = PartialNotification::new() .set_from_user_id(from_user) .set_on_elem_id(post_id) .set_on_elem_type(NotifElemType::POST) .set_type(action); push(&mut n) } /// Create & push friend notification pub fn create_friends_notification(from_user: &UserID, dest_user: &UserID, action: NotifEventType) -> ResultBoxError { let mut n = PartialNotification::new() .set_from_user_id(from_user) .set_dest_user_id(dest_user) .set_on_elem_id(from_user.id()) .set_on_elem_type(NotifElemType::FRIENDSHIP_REQUEST) .set_type(action); push(&mut n) } /// Create & push a group membership notification pub fn create_group_membership_notification(user_id: &UserID, moderator_id: Option<&UserID>, group_id: &GroupID, kind: NotifEventType) -> ResultBoxError { // Delete related group membership notifications delete_all_related_to_group_membership_notifications(user_id, group_id)?; let mut n = PartialNotification::new() .set_on_elem_id(group_id.id()) .set_on_elem_type(NotifElemType::GROUP_MEMBERSHIP) .set_type(kind); match moderator_id { // The notification must be sent to all the moderators of the group None => { n = n.set_from_user_id(user_id); } // We specify both the source and the destination of the notification // not to broadcast the notification to all the group members Some(moderator_id) => { n = n.set_from_user_id(moderator_id) .set_dest_user_id(user_id); } } push(&mut n) } /// Push a new notification fn push(n: &mut PartialNotification) -> ResultBoxError { if n.time_create.is_none() { n.time_create = Some(date_utils::time()); } // Determine the visibility level of the notification if matches!(n.on_elem_type, Some(NotifElemType::POST)) { let post = posts_helper::get_single(n.on_elem_id.unwrap())?; // Determine post container match &post.target_page { PostPageKind::PAGE_KIND_USER(user_id) => { n.container_type = Some(NotifElemType::USER_PAGE); n.container_id = Some(user_id.id()); } PostPageKind::PAGE_KIND_GROUP(group_id) => { n.container_type = Some(NotifElemType::GROUP_PAGE); n.container_id = Some(group_id.id()); } }; // Check the scope of the notification // Private post (on user page) if matches!(post.visibility, PostVisibilityLevel::VISIBILITY_USER) { // Check if the post belongs to current user => no notification needed if &post.user_id == post.user_page_id().unwrap() { return Ok(()); } // If the person who posted that is not the owner of the page if post.user_page_id().unwrap() != n.from_user_id.as_ref().unwrap() { n.dest_user_id = Some(post.user_page_id().unwrap().clone()); } // If the user is the owner of the page, but the post does not belongs to him else { n.dest_user_id = Some(post.user_id); } return push_private(n); } // Posts on user page else if post.is_on_user_page() { let mut friend_req = GetFriendsQuery::new(n.from_user_id.as_ref().unwrap()) .set_only_followers(true); // If the person who created the notification is not on his page if n.from_user_id.as_ref().unwrap() != post.user_page_id().unwrap() { friend_req = friend_req.set_only_common_with(post.user_page_id().unwrap()) } let friends: Vec = friend_req .exec()? .into_iter() .map(|f| f.friend_id) .collect(); return push_public(n, friends); } // Posts on group pages else if post.is_on_group_page() { return push_group_members(n, post.group_id().unwrap()); } // Unsupported scenario else { unimplemented!(); } } // Friendship notification else if matches!(n.on_elem_type, Some(NotifElemType::FRIENDSHIP_REQUEST)) { n.container_id = None; n.container_type = None; return push_private(n); } // Groups membership notifications else if matches!(n.on_elem_type, Some(NotifElemType::GROUP_MEMBERSHIP)) { // Complete the notification n.container_type = None; n.container_id = None; // Check whether the notification has to be pushed to a single user // or to all the moderators of the group return if let Some(_) = n.dest_user_id { // Push the notification in private way (if it has a destination, // generally the target user of the membership request) push_private(n) } else { push_group_moderators(n, &GroupID::new(n.on_elem_id.unwrap())) }; } else { unimplemented!(); } } /// Push a notification to group members fn push_group_members(n: &mut PartialNotification, group_id: &GroupID) -> ResultBoxError { let mut list = groups_helper::get_list_followers(group_id)?; list = list.into_iter().filter(|f| f != n.from_user_id.as_ref().unwrap()).collect(); push_public(n, list) } /// Push a notification to all the moderators & administrators of a group fn push_group_moderators(n: &mut PartialNotification, group_id: &GroupID) -> ResultBoxError { let list = groups_helper::get_list_members(group_id)?; let list: Vec = list .into_iter() .filter(|e| e.is_moderator()) .map(|f| f.user_id) .collect(); push_public(n, list) } /// Push a public notification fn push_public(n: &mut PartialNotification, users: Vec) -> ResultBoxError { n.visibility = Some(NotifEventVisibility::EVENT_PUBLIC); for user_id in users { n.dest_user_id = Some(user_id); if !similar_exists(n)? { create(n)?; } } Ok(()) } /// Push a private notification (to 1 user) fn push_private(n: &mut PartialNotification) -> ResultBoxError { n.visibility = Some(NotifEventVisibility::EVENT_PRIVATE); if !similar_exists(n)? { create(n)?; } Ok(()) } /// Create a new notification fn create(n: &PartialNotification) -> ResultBoxError { if n.dest_user_id.is_none() || n.from_user_id.is_none() { return Err(ExecError::boxed_new("Trying to send a notification without a source or a destination!")); } database::InsertQuery::new(NOTIFICATIONS_TABLE) .add_values(notif_to_db(n, true)) .insert_drop_result()?; // Send a notification (updated_number_conversations) events_helper::propagate_event(&Event::UpdatedNotificationsNumber(&vec![n.dest_user_id.clone().unwrap()]))?; Ok(()) } /// Delete notifications pub fn delete(notification: &PartialNotification) -> ResultBoxError { let conditions = notif_to_db(notification, false); // Get the list of affected users let users = database::QueryInfo::new(NOTIFICATIONS_TABLE) .add_conditions(&conditions) .add_field("dest_user_id") .exec(|r| r.get_user_id("dest_user_id"))?; // Delete the notifications database::DeleteQuery::new(NOTIFICATIONS_TABLE) .add_conditions(conditions) .exec()?; // Send a notification (updated_number_conversations) events_helper::propagate_event(&Event::UpdatedNotificationsNumber(&users))?; Ok(()) } /// Delete all the notifications of a given user pub fn delete_all_user(user_id: &UserID) -> ResultBoxError { delete(&PartialNotification::new().set_dest_user_id(user_id)) } /// Delete all the notifications related with a user pub fn delete_all_related_with_user(user_id: &UserID) -> ResultBoxError { // Delete all the notifications targeting the user delete_all_user(user_id)?; // Delete all the notifications created by the user delete(&PartialNotification::new().set_from_user_id(user_id))?; Ok(()) } /// Delete all the old notifications of a user pub fn clean_old_user_notifications(user: &User) -> Res { let lifetime = user.delete_notifications_after.unwrap_or(0); if lifetime < 1 { return Ok(()); } database::DeleteQuery::new(NOTIFICATIONS_TABLE) .cond_user_id("dest_user_id", &user.id) .set_custom_where("time_create < ?") .add_custom_where_arg_u64(time() - lifetime) .exec() } /// Delete all the notifications related with a group pub fn delete_all_related_with_group(group_id: &GroupID) -> ResultBoxError { delete(&PartialNotification::new() .set_on_elem_type(NotifElemType::GROUP_MEMBERSHIP) .set_on_elem_id(group_id.id()) )?; delete(&PartialNotification::new() .set_on_elem_type(NotifElemType::GROUP_PAGE) .set_on_elem_id(group_id.id()) ) } /// Delete all the notifications related to a group membership pub fn delete_all_related_to_group_membership_notifications(user_id: &UserID, group_id: &GroupID) -> ResultBoxError { let mut n = PartialNotification::new() .set_on_elem_type(NotifElemType::GROUP_MEMBERSHIP) .set_on_elem_id(group_id.id()); n.dest_user_id = Some(user_id.clone()); n.from_user_id = None; delete(&n)?; n.dest_user_id = None; n.from_user_id = Some(user_id.clone()); delete(&n)?; Ok(()) } /// Delete all the notifications about a post targeting a specified user pub fn delete_all_post_notifications_targeting_user(user_id: &UserID, post_id: PostID) -> ResultBoxError { let n = PartialNotification::new() .set_dest_user_id(user_id) .set_on_elem_type(NotifElemType::POST) .set_on_elem_id(post_id); delete(&n) } /// Delete all the notifications related with a post pub fn delete_all_related_with_post(post_id: PostID) -> ResultBoxError { let n = PartialNotification::new() .set_on_elem_type(NotifElemType::POST) .set_on_elem_id(post_id); delete(&n) } /// Delete all the notifications related with a friendship request pub fn delete_all_related_with_friendship_request(user_one: &UserID, user_two: &UserID) -> ResultBoxError { let mut n = PartialNotification::new() .set_on_elem_type(NotifElemType::FRIENDSHIP_REQUEST); n.from_user_id = Some(user_one.clone()); n.dest_user_id = Some(user_two.clone()); delete(&n)?; n.from_user_id = Some(user_two.clone()); n.dest_user_id = Some(user_one.clone()); delete(&n) } /// Check out whether a similar notification exists for given specifications pub fn similar_exists(n: &PartialNotification) -> ResultBoxError { database::QueryInfo::new(NOTIFICATIONS_TABLE) .add_conditions(¬if_to_db(n, false)) .exec_count() .map(|f| f > 0) } /// Count the number of unread notifications pub fn count_unread(user_id: &UserID) -> ResultBoxError { database::QueryInfo::new(NOTIFICATIONS_TABLE) .cond_user_id("dest_user_id", user_id) .cond_legacy_bool("seen", false) .exec_count() .map(|c| c as u64) } /// Get the list of notifications of the user pub fn get_list_unread(user_id: &UserID) -> ResultBoxError> { database::QueryInfo::new(NOTIFICATIONS_TABLE) .cond_user_id("dest_user_id", user_id) .cond_legacy_bool("seen", false) .set_order("id DESC") .exec(db_to_notif) } /// Get information about a single notification pub fn get_single(notif_id: u64) -> ResultBoxError { database::QueryInfo::new(NOTIFICATIONS_TABLE) .cond_u64("id", notif_id) .query_row(db_to_notif) } /// Turn a database row into a notification object fn db_to_notif(row: &database::RowResult) -> ResultBoxError { Ok(Notification { id: row.get_u64("id")?, time_create: row.get_u64("time_create")?, seen: row.get_legacy_bool("seen")?, from_user_id: row.get_user_id("from_user_id")?, dest_user_id: row.get_user_id("dest_user_id")?, on_elem_id: row.get_u64("on_elem_id")?, on_elem_type: NotifElemType::from_db(&row.get_str("on_elem_type")?), kind: NotifEventType::from_db(&row.get_str("type")?), visibility: NotifEventVisibility::from_db(&row.get_str("visibility")?), container_id: row.get_optional_u64("from_container_id")?, container_type: row.get_optional_str("from_container_type")? .map(|s| NotifElemType::from_db(&s)), }) } /// Turn a notification into a database entry fn notif_to_db(n: &PartialNotification, complete_information: bool) -> HashMap { let mut map = HashMap::new(); if let Some(id) = n.id { map.insert("id".to_string(), mysql::Value::UInt(id)); } if let Some(seen) = n.seen { map.insert("seen".to_string(), mysql::Value::Int(match seen { true => 1, false => 0, })); } if let Some(from_user_id) = &n.from_user_id { map.insert("from_user_id".to_string(), mysql::Value::UInt(from_user_id.id())); } if let Some(dest_user_id) = &n.dest_user_id { map.insert("dest_user_id".to_string(), mysql::Value::UInt(dest_user_id.id())); } if let Some(kind) = &n.kind { map.insert("type".to_string(), mysql::Value::from(kind.to_db())); } if let Some(on_elem_id) = n.on_elem_id { map.insert("on_elem_id".to_string(), mysql::Value::from(on_elem_id)); } if let Some(on_elem_type) = &n.on_elem_type { map.insert("on_elem_type".to_string(), mysql::Value::from(on_elem_type.to_db())); } if complete_information { if let Some(from_container_id) = n.container_id { map.insert("from_container_id".to_string(), mysql::Value::from(from_container_id)); } if let Some(from_container_type) = &n.container_type { map.insert("from_container_type".to_string(), mysql::Value::from(from_container_type.to_db())); } if let Some(time_create) = n.time_create { map.insert("time_create".to_string(), mysql::Value::from(time_create)); } if let Some(visibility) = &n.visibility { map.insert("visibility".to_string(), mysql::Value::from(visibility.to_db())); } } map }