diff --git a/src/controllers/comments_controller.rs b/src/controllers/comments_controller.rs index ba3882f..8e76a90 100644 --- a/src/controllers/comments_controller.rs +++ b/src/controllers/comments_controller.rs @@ -8,8 +8,9 @@ use crate::constants::PATH_COMMENTS_IMAGES; use crate::controllers::routes::RequestResult; use crate::data::comment::Comment; use crate::data::http_request_handler::HttpRequestHandler; +use crate::data::notification::NotifEventType; use crate::data::post::PostAccessLevel; -use crate::helpers::{comments_helper, posts_helper}; +use crate::helpers::{comments_helper, notifications_helper, posts_helper}; use crate::utils::date_utils::time; use crate::utils::string_utils::remove_html_nodes; @@ -45,7 +46,9 @@ pub fn create(r: &mut HttpRequestHandler) -> RequestResult { let comment_id = comments_helper::create(&comment)?; - // TODO : Create notifications + // Create notifications + notifications_helper::create_post_notification(&r.user_id()?, post.id, NotifEventType::COMMENT_CREATED)?; + // TODO : Remove notifications targeting current user about the post r.set_response(ResCreateComment::new(comment_id)) diff --git a/src/data/notification.rs b/src/data/notification.rs index 7e786e5..3b2bf36 100644 --- a/src/data/notification.rs +++ b/src/data/notification.rs @@ -211,6 +211,11 @@ impl PartialNotification { self.id.is_some() } + pub fn set_time_create(mut self, time: u64) -> PartialNotification { + self.time_create = Some(time); + self + } + pub fn set_dest_user_id(mut self, id: &UserID) -> PartialNotification { self.dest_user_id = Some(id.clone()); self @@ -230,4 +235,9 @@ impl PartialNotification { self.on_elem_type = Some(t); self } + + pub fn set_type(mut self, t: NotifEventType) -> PartialNotification { + self.kind = Some(t); + self + } } \ No newline at end of file diff --git a/src/data/post.rs b/src/data/post.rs index a94ed4b..77339ab 100644 --- a/src/data/post.rs +++ b/src/data/post.rs @@ -153,6 +153,11 @@ impl Post { _ => false, } } + + /// Check out whether a post is targeting a group page or not + pub fn is_on_group_page(&self) -> bool { + matches!(self.target_page, PostPageKind::PAGE_KIND_GROUP(_)) + } } #[cfg(test)] diff --git a/src/helpers/database.rs b/src/helpers/database.rs index b6ccb4a..c40030e 100644 --- a/src/helpers/database.rs +++ b/src/helpers/database.rs @@ -552,6 +552,13 @@ impl InsertQuery { } } + /// Add batch values + pub fn add_values(mut self, values: HashMap) -> Self { + self.values.extend(values.into_iter()); + self + } + + /// Add a string pub fn add_str(mut self, key: &str, value: &str) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value)); diff --git a/src/helpers/friends_helper.rs b/src/helpers/friends_helper.rs index da17020..787041b 100644 --- a/src/helpers/friends_helper.rs +++ b/src/helpers/friends_helper.rs @@ -16,8 +16,10 @@ use crate::helpers::database::QueryInfo; pub struct GetFriendsQuery { only_accepted: bool, only_following: bool, + only_followers: bool, target_user: UserID, friend_id: Option, + common_with_user: Option, } impl GetFriendsQuery { @@ -26,8 +28,10 @@ impl GetFriendsQuery { GetFriendsQuery { only_accepted: false, only_following: false, + only_followers: false, target_user: target_user.clone(), friend_id: None, + common_with_user: None, } } @@ -36,11 +40,24 @@ impl GetFriendsQuery { self } + /// Specify whether only the friends followed by friend_id should be selected pub fn set_only_following(mut self, following: bool) -> GetFriendsQuery { self.only_following = following; self } + /// Specify whether only the friends following friend_id should be selected + pub fn set_only_followers(mut self, following: bool) -> GetFriendsQuery { + self.only_followers = following; + self + } + + /// Specify whether all the returned friends should be common with a specified user + pub fn set_only_common_with(mut self, user: &UserID) -> GetFriendsQuery { + self.common_with_user = Some(user.clone()); + self + } + /// Get the list of friends pub fn exec(self) -> ResultBoxError> { get_list(&self) @@ -80,6 +97,26 @@ fn get_list(friend_query: &GetFriendsQuery) -> ResultBoxError> { query = query.cond_legacy_bool("f.abonnement", true); } + let mut custom_where = String::new(); + + if friend_query.only_followers { + custom_where = " EXISTS (SELECT * FROM amis d WHERE d.ID_personne = f.ID_amis AND d.ID_amis = f.ID_personne AND abonnement = 1) ".to_string(); + } + + if let Some(user_id) = &friend_query.common_with_user { + if !custom_where.is_empty() { + custom_where.push_str(" AND "); + } + + custom_where.push_str(" (f.ID_amis = ? OR EXISTS (SELECT * FROM amis d WHERE d.ID_personne = ? AND d.ID_amis = f.ID_amis AND actif = 1) ) "); + query = query.add_custom_where_argument_user_id(user_id); + query = query.add_custom_where_argument_user_id(user_id); + } + + if !custom_where.is_empty() { + query = query.set_custom_where(&custom_where); + } + query.exec(db_to_friend) } diff --git a/src/helpers/groups_helper.rs b/src/helpers/groups_helper.rs index 731053f..1d43775 100644 --- a/src/helpers/groups_helper.rs +++ b/src/helpers/groups_helper.rs @@ -313,6 +313,15 @@ pub fn get_access_level(group_id: &GroupID, user_id: Option) -> ResultBo Ok(GroupAccessLevel::NO_ACCESS) } +/// Get the list of follower of a given group +pub fn get_list_followers(group_id: &GroupID) -> ResultBoxError> { + database::QueryInfo::new(GROUPS_MEMBERS_TABLE) + .cond_group_id("groups_id", group_id) + .cond_legacy_bool("following", true) + .add_field("user_id") + .exec(|r| r.get_user_id("user_id")) +} + /// Count the number of members of a group pub fn count_members(group_id: &GroupID) -> ResultBoxError { database::QueryInfo::new(GROUPS_MEMBERS_TABLE) diff --git a/src/helpers/notifications_helper.rs b/src/helpers/notifications_helper.rs index b4b1a4f..5cb8bf7 100644 --- a/src/helpers/notifications_helper.rs +++ b/src/helpers/notifications_helper.rs @@ -8,8 +8,147 @@ use crate::constants::database_tables_names::NOTIFICATIONS_TABLE; use crate::data::error::ResultBoxError; use crate::data::group_id::GroupID; use crate::data::notification::{NotifElemType, NotifEventType, NotifEventVisibility, Notification, PartialNotification}; +use crate::data::post::{PostPageKind, PostVisibilityLevel}; use crate::data::user::UserID; -use crate::helpers::database; +use crate::helpers::{database, groups_helper, posts_helper}; +use crate::helpers::friends_helper::GetFriendsQuery; +use crate::utils::date_utils; + +/// Create post notification +pub fn create_post_notification(from_user: &UserID, post_id: u64, action: NotifEventType) -> ResultBoxError { + let mut n = PartialNotification::new() + .set_time_create(date_utils::time()) + .set_from_user_id(from_user) + .set_on_elem_id(post_id) + .set_on_elem_type(NotifElemType::POST) + .set_type(action); + + push(&mut n) +} + +/// Push a new notification +pub 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!(); + } + } else { + unimplemented!(); + } +} + +/// Push a notification to group members +pub 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 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 { + database::InsertQuery::new(NOTIFICATIONS_TABLE) + .add_values(notif_to_db(n, true)) + .insert_drop_result()?; + + // TODO : WebSocket : trigger notify system + + Ok(()) +} + /// Delete notifications pub fn delete(notification: &PartialNotification) -> ResultBoxError {