From 87718076e795aeaaeaa9d3ebb558685f409ad53d Mon Sep 17 00:00:00 2001
From: Pierre HUBERT <pierre.git@communiquons.org>
Date: Sat, 11 Jul 2020 08:14:30 +0200
Subject: [PATCH] Can get the list of unread notifications

---
 src/api_data/mod.rs                         |   3 +-
 src/api_data/notification_api.rs            |  59 ++++++++
 src/controllers/notifications_controller.rs |   8 +
 src/controllers/routes.rs                   |   3 +
 src/data/mod.rs                             |   3 +-
 src/data/notification.rs                    | 154 ++++++++++++++++++++
 src/helpers/database.rs                     |   8 +
 src/helpers/notifications_helper.rs         |  28 ++++
 8 files changed, 264 insertions(+), 2 deletions(-)
 create mode 100644 src/api_data/notification_api.rs
 create mode 100644 src/data/notification.rs

diff --git a/src/api_data/mod.rs b/src/api_data/mod.rs
index ed81ad2..a40a87a 100644
--- a/src/api_data/mod.rs
+++ b/src/api_data/mod.rs
@@ -39,4 +39,5 @@ pub mod res_create_post;
 pub mod posts_targets_api;
 pub mod res_create_comment;
 pub mod res_number_unread_notifications;
-pub mod res_count_all_unreads;
\ No newline at end of file
+pub mod res_count_all_unreads;
+pub mod notification_api;
\ No newline at end of file
diff --git a/src/api_data/notification_api.rs b/src/api_data/notification_api.rs
new file mode 100644
index 0000000..29cb6e3
--- /dev/null
+++ b/src/api_data/notification_api.rs
@@ -0,0 +1,59 @@
+//! # API Notification object
+//!
+//! @author Pierre Hubert
+
+use serde::{Serialize, Serializer};
+use serde::ser::SerializeMap;
+
+use crate::data::notification::Notification;
+
+struct TypeContainer {
+    t: String,
+}
+
+impl Serialize for TypeContainer {
+    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+        S: Serializer {
+        let mut map = serializer.serialize_map(Some(1))?;
+        map.serialize_entry("type", &self.t)?;
+        map.end()
+    }
+}
+
+#[derive(Serialize)]
+pub struct NotificationAPI {
+    id: u64,
+    time_create: u64,
+    seen: bool,
+    from_user_id: u64,
+    dest_user_id: u64,
+    on_elem_id: u64,
+    on_elem_type: String,
+    #[serde(flatten)]
+    type_container: TypeContainer,
+    event_visibility: String,
+    from_container_id: Option<u64>,
+    from_container_type: Option<String>,
+}
+
+impl NotificationAPI {
+    pub fn new(n: &Notification) -> NotificationAPI {
+        NotificationAPI {
+            id: n.id,
+            time_create: n.time_create,
+            seen: n.seen,
+            from_user_id: n.from_user_id.id(),
+            dest_user_id: n.dest_user_id.id(),
+            on_elem_id: n.on_elem_id,
+            on_elem_type: n.on_elem_type.to_api(),
+            type_container: TypeContainer { t: n.kind.to_api() },
+            event_visibility: n.visibility.to_api(),
+            from_container_id: n.container_id,
+            from_container_type: n.container_type.as_ref().map(|f| f.to_api()),
+        }
+    }
+
+    pub fn for_list(l: &Vec<Notification>) -> Vec<NotificationAPI> {
+        l.iter().map(Self::new).collect()
+    }
+}
\ No newline at end of file
diff --git a/src/controllers/notifications_controller.rs b/src/controllers/notifications_controller.rs
index 79fd007..b020825 100644
--- a/src/controllers/notifications_controller.rs
+++ b/src/controllers/notifications_controller.rs
@@ -2,6 +2,7 @@
 //!
 //! @author Pierre Hubert
 
+use crate::api_data::notification_api::NotificationAPI;
 use crate::api_data::res_count_all_unreads::ResCountAllUnread;
 use crate::api_data::res_number_unread_notifications::ResNumberUnreadNotifications;
 use crate::controllers::routes::RequestResult;
@@ -24,4 +25,11 @@ pub fn count_all_news(r: &mut HttpRequestHandler) -> RequestResult {
     };
 
     r.set_response(ResCountAllUnread::new(notifications, conversations as u64, friends_requests))
+}
+
+/// Get the list of unread notifications
+pub fn get_list_unread(r: &mut HttpRequestHandler) -> RequestResult {
+    let list = notifications_helper::get_list_unread(r.user_id_ref()?)?;
+
+    r.set_response(NotificationAPI::for_list(&list))
 }
\ No newline at end of file
diff --git a/src/controllers/routes.rs b/src/controllers/routes.rs
index 60bb809..3123acb 100644
--- a/src/controllers/routes.rs
+++ b/src/controllers/routes.rs
@@ -249,6 +249,9 @@ pub fn get_routes() -> Vec<Route> {
 
         Route::post("/notifications/count_all_news", Box::new(notifications_controller::count_all_news)),
 
+        Route::post("/notifications/get_list_unread", Box::new(notifications_controller::get_list_unread)),
+
+
         // Movies controller
         Route::post("/movies/get_list", Box::new(movies_controller::get_list)),
 
diff --git a/src/data/mod.rs b/src/data/mod.rs
index 8b40982..9020438 100644
--- a/src/data/mod.rs
+++ b/src/data/mod.rs
@@ -23,4 +23,5 @@ pub mod post;
 pub mod movie;
 pub mod survey;
 pub mod comment;
-pub mod new_survey;
\ No newline at end of file
+pub mod new_survey;
+pub mod notification;
\ No newline at end of file
diff --git a/src/data/notification.rs b/src/data/notification.rs
new file mode 100644
index 0000000..97c615f
--- /dev/null
+++ b/src/data/notification.rs
@@ -0,0 +1,154 @@
+//! # Notification
+//!
+//! @author Pierre Hubert
+
+use crate::data::user::UserID;
+
+#[allow(non_camel_case_types)]
+pub enum NotifElemType {
+    UNKNOWN,
+    USER_PAGE,
+    GROUP_PAGE,
+    CONVERSATION,
+    CONVERSATION_MESSAGE,
+    POST,
+    COMMENT,
+    FRIENDSHIP_REQUEST,
+    GROUP_MEMBERSHIP,
+}
+
+impl NotifElemType {
+    pub fn from_db(d: &str) -> NotifElemType {
+        match d {
+            "user_page" => NotifElemType::USER_PAGE,
+            "group_page" => NotifElemType::GROUP_PAGE,
+            "conversation" => NotifElemType::CONVERSATION,
+            "conversation_message" => NotifElemType::CONVERSATION_MESSAGE,
+            "post" => NotifElemType::POST,
+            "comment" => NotifElemType::COMMENT,
+            "friend_request" => NotifElemType::FRIENDSHIP_REQUEST,
+            "group_membership" => NotifElemType::GROUP_MEMBERSHIP,
+            _ => NotifElemType::UNKNOWN,
+        }
+    }
+
+    pub fn to_db(&self) -> String {
+        match self {
+            NotifElemType::USER_PAGE => "user_page",
+            NotifElemType::GROUP_PAGE => "group_page",
+            NotifElemType::CONVERSATION => "conversation",
+            NotifElemType::CONVERSATION_MESSAGE => "conversation_message",
+            NotifElemType::POST => "post",
+            NotifElemType::COMMENT => "comment",
+            NotifElemType::FRIENDSHIP_REQUEST => "friend_request",
+            NotifElemType::GROUP_MEMBERSHIP => "group_membership",
+            NotifElemType::UNKNOWN => "",
+        }.to_string()
+    }
+
+    pub fn to_api(&self) -> String {
+        self.to_db()
+    }
+}
+
+
+#[allow(non_camel_case_types)]
+pub enum NotifEventType {
+    UNKNOWN,
+    COMMENT_CREATED,
+    SENT_FRIEND_REQUEST,
+    ACCEPTED_FRIEND_REQUEST,
+    REJECTED_FRIEND_REQUEST,
+    ELEM_CREATED,
+    ELEM_UPDATED,
+    SENT_GROUP_MEMBERSHIP_INVITATION,
+    ACCEPTED_GROUP_MEMBERSHIP_INVITATION,
+    REJECTED_GROUP_MEMBERSHIP_INVITATION,
+    SENT_GROUP_MEMBERSHIP_REQUEST,
+    ACCEPTED_GROUP_MEMBERSHIP_REQUEST,
+    REJECTED_GROUP_MEMBERSHIP_REQUEST,
+}
+
+impl NotifEventType {
+    pub fn from_db(d: &str) -> NotifEventType {
+        match d {
+            "comment_created" => NotifEventType::COMMENT_CREATED,
+            "sent_friend_request" => NotifEventType::SENT_FRIEND_REQUEST,
+            "accepted_friend_request" => NotifEventType::ACCEPTED_FRIEND_REQUEST,
+            "rejected_friend_request" => NotifEventType::REJECTED_FRIEND_REQUEST,
+            "elem_created" => NotifEventType::ELEM_CREATED,
+            "elem_updated" => NotifEventType::ELEM_UPDATED,
+            "sent_group_membership_invitation" => NotifEventType::SENT_GROUP_MEMBERSHIP_INVITATION,
+            "accepted_group_membership_invitation" => NotifEventType::ACCEPTED_GROUP_MEMBERSHIP_INVITATION,
+            "rejected_group_membership_invitation" => NotifEventType::REJECTED_GROUP_MEMBERSHIP_INVITATION,
+            "sent_group_membership_request" => NotifEventType::SENT_GROUP_MEMBERSHIP_REQUEST,
+            "accepted_group_membership_request" => NotifEventType::ACCEPTED_GROUP_MEMBERSHIP_REQUEST,
+            "rejected_group_membership_request" => NotifEventType::REJECTED_GROUP_MEMBERSHIP_REQUEST,
+            _ => NotifEventType::UNKNOWN,
+        }
+    }
+
+    pub fn to_db(&self) -> String {
+        match self {
+            NotifEventType::COMMENT_CREATED => "comment_created",
+            NotifEventType::SENT_FRIEND_REQUEST => "sent_friend_request",
+            NotifEventType::ACCEPTED_FRIEND_REQUEST => "accepted_friend_request",
+            NotifEventType::REJECTED_FRIEND_REQUEST => "rejected_friend_request",
+            NotifEventType::ELEM_CREATED => "elem_created",
+            NotifEventType::ELEM_UPDATED => "elem_updated",
+            NotifEventType::SENT_GROUP_MEMBERSHIP_INVITATION => "sent_group_membership_invitation",
+            NotifEventType::ACCEPTED_GROUP_MEMBERSHIP_INVITATION => "accepted_group_membership_invitation",
+            NotifEventType::REJECTED_GROUP_MEMBERSHIP_INVITATION => "rejected_group_membership_invitation",
+            NotifEventType::SENT_GROUP_MEMBERSHIP_REQUEST => "sent_group_membership_request",
+            NotifEventType::ACCEPTED_GROUP_MEMBERSHIP_REQUEST => "accepted_group_membership_request",
+            NotifEventType::REJECTED_GROUP_MEMBERSHIP_REQUEST => "rejected_group_membership_request",
+            NotifEventType::UNKNOWN => ""
+        }.to_string()
+    }
+
+    pub fn to_api(&self) -> String {
+        self.to_db()
+    }
+}
+
+
+#[allow(non_camel_case_types)]
+pub enum NotifEventVisibility {
+    EVENT_PRIVATE,
+    EVENT_PUBLIC,
+}
+
+impl NotifEventVisibility {
+    pub fn from_db(d: &str) -> NotifEventVisibility {
+        match d {
+            "event_public" => NotifEventVisibility::EVENT_PUBLIC,
+            "event_private" => NotifEventVisibility::EVENT_PRIVATE,
+            _ => NotifEventVisibility::EVENT_PRIVATE,
+        }
+    }
+
+    pub fn to_db(&self) -> String {
+        match self {
+            NotifEventVisibility::EVENT_PUBLIC => "event_public",
+            NotifEventVisibility::EVENT_PRIVATE => "event_private",
+        }.to_string()
+    }
+
+    pub fn to_api(&self) -> String {
+        self.to_db()
+    }
+}
+
+pub struct Notification {
+    pub id: u64,
+    pub time_create: u64,
+    pub seen: bool,
+    pub from_user_id: UserID,
+    pub dest_user_id: UserID,
+    pub on_elem_id: u64,
+    pub on_elem_type: NotifElemType,
+    pub kind: NotifEventType,
+    pub visibility: NotifEventVisibility,
+    pub container_id: Option<u64>,
+    pub container_type: Option<NotifElemType>,
+}
\ No newline at end of file
diff --git a/src/helpers/database.rs b/src/helpers/database.rs
index aa6174a..02e5eb5 100644
--- a/src/helpers/database.rs
+++ b/src/helpers/database.rs
@@ -304,6 +304,14 @@ impl<'a> RowResult<'a> {
         }
     }
 
+    /// Get an optional unsigned number  => Set to None if value is null / empty
+    pub fn get_optional_u64(&self, name: &str) -> ResultBoxError<Option<u64>> {
+        match self.is_null(name)? {
+            true => Ok(None),
+            false => Ok(Some(self.get_u64(name)?))
+        }
+    }
+
     pub fn get_u32(&self, name: &str) -> ResultBoxError<u32> {
         let value = self.row.get_opt(self.find_col(name)?);
 
diff --git a/src/helpers/notifications_helper.rs b/src/helpers/notifications_helper.rs
index da07f64..936a66b 100644
--- a/src/helpers/notifications_helper.rs
+++ b/src/helpers/notifications_helper.rs
@@ -4,6 +4,7 @@
 
 use crate::constants::database_tables_names::NOTIFICATIONS_TABLE;
 use crate::data::error::ResultBoxError;
+use crate::data::notification::{NotifElemType, NotifEventType, NotifEventVisibility, Notification};
 use crate::data::user::UserID;
 use crate::helpers::database;
 
@@ -14,4 +15,31 @@ pub fn count_unread(user_id: &UserID) -> ResultBoxError<u64> {
         .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<Vec<Notification>> {
+    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)
+}
+
+/// Turn a database row into a notification object
+fn db_to_notif(row: &database::RowResult) -> ResultBoxError<Notification> {
+    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)),
+    })
 }
\ No newline at end of file