summaryrefslogtreecommitdiff
path: root/src/graphql
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2024-09-03 20:14:58 +0200
committerSanto Cariotti <santo@dcariotti.me>2024-09-03 20:15:52 +0200
commit32ba33078c2970b8658425260de287d6cde0db82 (patch)
tree3b3886a8eb2377fe021a054896cd5b7021b2e92e /src/graphql
parente6cadc73edf20b4f959e8811cf7944d57fe6a5da (diff)
Add notification type
Diffstat (limited to 'src/graphql')
-rw-r--r--src/graphql/query.rs21
-rw-r--r--src/graphql/types/mod.rs1
-rw-r--r--src/graphql/types/notification.rs135
3 files changed, 157 insertions, 0 deletions
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))
+ }
+ }
+}