summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2024-08-22 22:25:57 +0200
committerSanto Cariotti <santo@dcariotti.me>2024-08-22 22:26:24 +0200
commitfc51ff9e22a809e257ae92f12272f1dbcb31f594 (patch)
treeb1e0fcd8e9ca931f7d89b976eaf2a40388e98f6c
parent8738cf2c6b1ce9f99e3399f35ba9f49832ffed52 (diff)
Add position type and query on it
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml2
-rw-r--r--schema/init.sql10
-rw-r--r--src/dates.rs35
-rw-r--r--src/graphql/query.rs12
-rw-r--r--src/graphql/types/jwt.rs2
-rw-r--r--src/graphql/types/mod.rs1
-rw-r--r--src/graphql/types/position.rs74
-rw-r--r--src/main.rs1
9 files changed, 135 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index df102b6..3a7dd05 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -437,6 +437,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
+ "serde",
"wasm-bindgen",
"windows-targets 0.52.6",
]
diff --git a/Cargo.toml b/Cargo.toml
index 8391d64..4570036 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,6 @@ serde = { version = "1.0.208", features = ["derive"] }
tokio-postgres = "0.7.11"
jsonwebtoken = "9.3.0"
once_cell = "1.19.0"
-chrono = "0.4.38"
+chrono = { version = "0.4.38", features = ["serde"] }
sha256 = "1.5.0"
axum-extra = { version = "0.9.3", features = ["typed-header"] }
diff --git a/schema/init.sql b/schema/init.sql
index 38d1e80..f2bc431 100644
--- a/schema/init.sql
+++ b/schema/init.sql
@@ -5,3 +5,13 @@ CREATE TABLE users(
is_admin boolean default false,
PRIMARY KEY (id)
);
+
+CREATE TABLE positions(
+ id SERIAL NOT NULL,
+ user_id INTEGER NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ location GEOGRAPHY(Point, 4326) NOT NULL,
+ PRIMARY KEY(id),
+ CONSTRAINT fk_users_id
+ FOREIGN KEY(user_id) REFERENCES users(id)
+);
diff --git a/src/dates.rs b/src/dates.rs
new file mode 100644
index 0000000..87e6f5e
--- /dev/null
+++ b/src/dates.rs
@@ -0,0 +1,35 @@
+use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
+use chrono::{DateTime, Utc};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct GraphQLDate(pub DateTime<Utc>);
+
+impl From<DateTime<Utc>> for GraphQLDate {
+ fn from(dt: DateTime<Utc>) -> Self {
+ GraphQLDate(dt)
+ }
+}
+
+impl From<GraphQLDate> for DateTime<Utc> {
+ fn from(my_dt: GraphQLDate) -> Self {
+ my_dt.0
+ }
+}
+
+#[Scalar]
+impl ScalarType for GraphQLDate {
+ fn parse(value: Value) -> InputValueResult<Self> {
+ if let Value::String(s) = &value {
+ DateTime::parse_from_rfc3339(s)
+ .map(|dt| GraphQLDate(dt.with_timezone(&Utc)))
+ .map_err(|e| InputValueError::custom(format!("Invalid DateTime: {}", e)))
+ } else {
+ Err(InputValueError::expected_type(value))
+ }
+ }
+
+ fn to_value(&self) -> Value {
+ Value::String(self.0.to_rfc3339())
+ }
+}
diff --git a/src/graphql/query.rs b/src/graphql/query.rs
index 0e19771..a875d25 100644
--- a/src/graphql/query.rs
+++ b/src/graphql/query.rs
@@ -1,4 +1,4 @@
-use crate::{errors::AppError, graphql::types::user};
+use crate::graphql::types::{position, user};
use async_graphql::{Context, Object};
pub struct Query;
@@ -18,4 +18,14 @@ impl Query {
) -> Result<Option<Vec<user::User>>, String> {
user::get_users(ctx, limit, offset).await
}
+
+ /// Returns all the positions
+ async fn positions<'ctx>(
+ &self,
+ ctx: &Context<'ctx>,
+ #[graphql(desc = "Limit results")] limit: Option<i64>,
+ #[graphql(desc = "Offset results")] offset: Option<i64>,
+ ) -> Result<Option<Vec<position::Position>>, String> {
+ position::get_positions(ctx, limit, offset).await
+ }
}
diff --git a/src/graphql/types/jwt.rs b/src/graphql/types/jwt.rs
index c118622..475b1bd 100644
--- a/src/graphql/types/jwt.rs
+++ b/src/graphql/types/jwt.rs
@@ -32,7 +32,7 @@ impl Keys {
/// Claims struct.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Claims {
- user_id: i32,
+ pub user_id: i32,
exp: usize,
}
diff --git a/src/graphql/types/mod.rs b/src/graphql/types/mod.rs
index d7cdece..a77cf8c 100644
--- a/src/graphql/types/mod.rs
+++ b/src/graphql/types/mod.rs
@@ -1,2 +1,3 @@
pub mod jwt;
+pub mod position;
pub mod user;
diff --git a/src/graphql/types/position.rs b/src/graphql/types/position.rs
new file mode 100644
index 0000000..e5ea8cd
--- /dev/null
+++ b/src/graphql/types/position.rs
@@ -0,0 +1,74 @@
+use crate::{dates::GraphQLDate, state::AppState};
+use async_graphql::{Context, Object};
+use chrono::Utc;
+use serde::{Deserialize, Serialize};
+
+use super::jwt::Authentication;
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct Position {
+ pub id: i32,
+ pub user_id: i32,
+ pub created_at: GraphQLDate,
+ pub latitude: f64,
+ pub longitude: f64,
+}
+
+#[Object]
+impl Position {
+ async fn id(&self) -> i32 {
+ self.id
+ }
+
+ async fn user_id(&self) -> i32 {
+ self.user_id
+ }
+
+ async fn created_at(&self) -> GraphQLDate {
+ self.created_at.clone()
+ }
+
+ async fn latitude(&self) -> f64 {
+ self.latitude
+ }
+
+ async fn longitude(&self) -> f64 {
+ self.longitude
+ }
+}
+
+pub async fn get_positions<'ctx>(
+ ctx: &Context<'ctx>,
+ limit: Option<i64>,
+ offset: Option<i64>,
+) -> Result<Option<Vec<Position>>, 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 rows = client.query("
+ SELECT id, user_id, created_at, ST_Y(location::geometry) AS latitude, ST_X(location::geometry) AS longitude
+ FROM positions
+ WHERE user_id = $1
+ ORDER BY id DESC
+ LIMIT $2
+ OFFSET $3",
+ &[&claims.user_id, &limit.unwrap_or(20), &offset.unwrap_or(0)]).await.unwrap();
+
+ let positions: Vec<Position> = rows
+ .iter()
+ .map(|row| Position {
+ id: row.get("id"),
+ user_id: row.get("user_id"),
+ created_at: GraphQLDate(Utc::now()),
+ latitude: row.get("latitude"),
+ longitude: row.get("longitude"),
+ })
+ .collect();
+
+ Ok(Some(positions))
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 8dfc145..e53395a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
mod config;
+mod dates;
mod db;
mod errors;
mod graphql;