From 32ba33078c2970b8658425260de287d6cde0db82 Mon Sep 17 00:00:00 2001
From: Santo Cariotti <santo@dcariotti.me>
Date: Tue, 3 Sep 2024 20:14:58 +0200
Subject: Add notification type

---
 src/graphql/query.rs              |  21 ++++++
 src/graphql/types/mod.rs          |   1 +
 src/graphql/types/notification.rs | 135 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 157 insertions(+)
 create mode 100644 src/graphql/types/notification.rs

(limited to 'src')

diff --git a/src/graphql/query.rs b/src/graphql/query.rs
index c122220..2e2466f 100644
--- a/src/graphql/query.rs
+++ b/src/graphql/query.rs
@@ -103,4 +103,25 @@ impl Query {
     ) -> Result<Option<Vec<alert::Alert>>, String> {
         alert::get_alerts(ctx, id, limit, offset).await
     }
+
+    /// Returns all the notifications. They can be filtered by an alert id.
+    ///
+    /// Request example:
+    /// ```text
+    /// curl http://localhost:8000/graphql
+    /// -H 'authorization: Bearer ***'
+    /// -H 'content-type: application/json'
+    /// -d '{"query":"{notifications(alertId: 1) {
+    /// id, alert { id, userId, createdAt, area, extendedArea, level, reachedUsers }, position {id, userId, createdAt, latitude, longitude, movingActivity}, seen, createdAt
+    /// }}"}'
+    /// ```
+    async fn notifications<'ctx>(
+        &self,
+        ctx: &Context<'ctx>,
+        #[graphql(desc = "Filter by alert ID")] alert_id: Option<i32>,
+        #[graphql(desc = "Limit results")] limit: Option<i64>,
+        #[graphql(desc = "Offset results")] offset: Option<i64>,
+    ) -> Result<Option<Vec<notification::Notification>>, String> {
+        notification::get_notifications(ctx, alert_id, limit, offset).await
+    }
 }
diff --git a/src/graphql/types/mod.rs b/src/graphql/types/mod.rs
index d0f8ead..6f19bb4 100644
--- a/src/graphql/types/mod.rs
+++ b/src/graphql/types/mod.rs
@@ -1,4 +1,5 @@
 pub mod alert;
 pub mod jwt;
+pub mod notification;
 pub mod position;
 pub mod user;
diff --git a/src/graphql/types/notification.rs b/src/graphql/types/notification.rs
new file mode 100644
index 0000000..c73fe66
--- /dev/null
+++ b/src/graphql/types/notification.rs
@@ -0,0 +1,135 @@
+use crate::{
+    graphql::types::{alert::Alert, jwt::Authentication, position::Position, user::find_user},
+    state::AppState,
+};
+use async_graphql::{Context, SimpleObject};
+use serde::{Deserialize, Serialize};
+
+#[derive(SimpleObject, Clone, Debug, Serialize, Deserialize)]
+/// Notification struct
+pub struct Notification {
+    pub id: i32,
+    pub alert: Alert,
+    pub position: Position,
+    pub seen: bool,
+    pub created_at: i64,
+}
+/// Get notifications from the database
+pub async fn get_notifications<'ctx>(
+    ctx: &Context<'ctx>,
+
+    // Optional filter by id.
+    alert_id: Option<i32>,
+
+    // Optional limit results
+    limit: Option<i64>,
+
+    // Optional offset results. It should be used with limit field.
+    offset: Option<i64>,
+) -> Result<Option<Vec<Notification>>, String> {
+    let state = ctx.data::<AppState>().expect("Can't connect to db");
+    let client = &*state.client;
+    let auth: &Authentication = ctx.data().unwrap();
+    match auth {
+        Authentication::NotLogged => Err("Unauthorized".to_string()),
+        Authentication::Logged(claims) => {
+            let claim_user = find_user(client, claims.user_id)
+                .await
+                .expect("Should not be here");
+
+            let limit = limit.unwrap_or(20);
+            let offset = offset.unwrap_or(0);
+
+            let mut base_query = "SELECT n.id,
+                                n.alert_id,
+                                n.position_id,
+                                n.seen,
+                                extract(epoch from n.created_at)::double precision as created_at,
+                                a.id as alert_id,
+                                a.user_id as alert_user_id,
+                                extract(epoch from a.created_at)::double precision as alert_created_at,
+                                ST_AsText(a.area) as alert_area,
+                                ST_AsText(
+                                    ST_Buffer(
+                                        a.area::geography,
+                                        CASE
+                                            WHEN level = 'One' THEN 0
+                                            WHEN level = 'Two' THEN 1000
+                                            WHEN level = 'Three' THEN 2000
+                                            ELSE 0
+                                        END
+                                    )
+                                ) as alert_extended_area,
+                                a.level as alert_level,
+                                a.reached_users as alert_reached_users,
+                                p.id as position_id,
+                                p.user_id as position_user_id,
+                                extract(epoch from p.created_at)::double precision as position_created_at,
+                                ST_Y(p.location::geometry) AS position_latitude,
+                                ST_X(p.location::geometry) AS position_longitude,
+                                p.activity as position_activity
+                        FROM notifications n
+                        JOIN alerts a ON n.alert_id = a.id
+                        JOIN positions p ON n.position_id = p.id".to_string();
+
+            let rows = match alert_id {
+                Some(id) if claim_user.is_admin => 
+                        client
+                        .query(&format!(
+                            "{base_query} WHERE n.alert_id = $1 ORDER BY n.id DESC LIMIT $2 OFFSET $3",
+                        ), &[&id, &limit, &offset])
+                        .await
+                        .unwrap(),
+                Some (id) =>
+                    client
+                    .query(&format!(
+                        "{base_query} WHERE p.user_id = $1 AND n.alert_id = $2 ORDER BY n.id DESC LIMIT $3 OFFSET $4",
+                    ), &[&claim_user.id, &id, &limit, &offset])
+                    .await
+                    .unwrap(),
+                None if claim_user.is_admin => client
+                    .query(
+                        &format!("{base_query} ORDER BY n.id DESC LIMIT $1 OFFSET $2"),
+                        &[&limit, &offset],
+                    )
+                    .await
+                    .unwrap(),
+                None =>
+                    client.query(
+                        &format!("{base_query} WHERE p.user_id = $1 ORDER BY n.id DESC LIMIT $2 OFFSET $3"),
+                        &[&claim_user.id, &limit, &offset],
+                    )
+                    .await
+                    .unwrap(),
+            };
+
+            let notifications: Vec<Notification> = rows
+                .iter()
+                .map(|row| Notification {
+                    id: row.get("id"),
+                    alert: Alert {
+                        id: row.get("alert_id"),
+                        user_id: row.get("alert_user_id"),
+                        created_at: row.get::<_, f64>("alert_created_at") as i64,
+                        area: row.get("alert_area"),
+                        extended_area: row.get("alert_extended_area"),
+                        level: row.get("alert_level"),
+                        reached_users: row.get("alert_reached_users"),
+                    },
+                    position: Position {
+                        id: row.get("position_id"),
+                        user_id: row.get("position_user_id"),
+                        created_at: row.get::<_, f64>("position_created_at") as i64,
+                        latitude: row.get("position_latitude"),
+                        longitude: row.get("position_longitude"),
+                        moving_activity: row.get("position_activity"),
+                    },
+                    seen: row.get("seen"),
+                    created_at: row.get::<_, f64>("created_at") as i64,
+                })
+                .collect();
+
+            Ok(Some(notifications))
+        }
+    }
+}
-- 
cgit v1.2.3-18-g5258