Skip to content

Commit dae79c1

Browse files
committed
Move to a better way to support surrealdb objects with our http endpoints
1 parent 5a73689 commit dae79c1

File tree

5 files changed

+115
-85
lines changed

5 files changed

+115
-85
lines changed

src/api/post.rs

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use std::path::PathBuf;
2-
use actix_web::{Error, get, HttpResponse, patch, put, Scope, web, post, delete};
1+
use crate::definitions::{Post, PostData, User};
2+
use crate::storage::database_manager::DatabaseManager;
3+
use crate::storage::storage_manager::StorageManager;
34
use actix_web::web::Json;
5+
use actix_web::{delete, get, patch, post, put, web, Error, HttpResponse, Scope};
46
use futures_util::StreamExt;
57
use log::error;
6-
use crate::definitions::{BodyPost, BodyUser, Post, User};
7-
use crate::storage::database_manager::DatabaseManager;
8-
use crate::storage::storage_manager::StorageManager;
8+
use std::path::PathBuf;
99

1010
pub fn blog_service() -> Scope {
1111
web::scope("/api/v1/posts")
@@ -18,10 +18,10 @@ pub fn blog_service() -> Scope {
1818
#[get("")]
1919
async fn posts_get(
2020
user: User,
21-
database_manager: web::Data<DatabaseManager>) -> Result<HttpResponse, Error> {
22-
21+
database_manager: web::Data<DatabaseManager>,
22+
) -> Result<HttpResponse, Error> {
2323
if !user.admin {
24-
return Ok(HttpResponse::Unauthorized().finish())
24+
return Ok(HttpResponse::Unauthorized().finish());
2525
}
2626

2727
let posts = match database_manager.fetch_posts().await {
@@ -36,71 +36,56 @@ async fn posts_get(
3636
#[get("/{postId}")]
3737
async fn post_get(
3838
path: web::Path<String>,
39-
database_manager: web::Data<DatabaseManager>) -> Result<HttpResponse, Error> {
40-
39+
database_manager: web::Data<DatabaseManager>,
40+
) -> Result<HttpResponse, Error> {
4141
let post_id = path.into_inner();
4242

4343
let found_post: Option<Post> = match database_manager.fetch_post(post_id).await {
44-
Ok(found_post) => {
45-
found_post
46-
}
44+
Ok(found_post) => found_post,
4745
Err(_) => {
4846
return Ok(HttpResponse::InternalServerError().finish());
4947
}
5048
};
5149

5250
match found_post {
53-
Some(found_user) => {
54-
Ok(HttpResponse::Ok().json(found_user))
55-
}
56-
None => {
57-
Ok(HttpResponse::NotFound().finish())
58-
}
51+
Some(found_user) => Ok(HttpResponse::Ok().json(found_user)),
52+
None => Ok(HttpResponse::NotFound().finish()),
5953
}
6054
}
6155

6256
#[delete("/{postId}")]
6357
async fn post_delete(
6458
user: User,
6559
path: web::Path<String>,
66-
database_manager: web::Data<DatabaseManager>) -> Result<HttpResponse, Error> {
67-
60+
database_manager: web::Data<DatabaseManager>,
61+
) -> Result<HttpResponse, Error> {
6862
let post_id = path.into_inner();
6963

7064
if !user.admin {
71-
return Ok(HttpResponse::Unauthorized().finish())
65+
return Ok(HttpResponse::Unauthorized().finish());
7266
}
7367

7468
match database_manager.delete_post(post_id).await {
75-
Ok(_) => {
76-
Ok(HttpResponse::Ok().finish())
77-
},
78-
Err(_) => {
79-
Ok(HttpResponse::InternalServerError().finish())
80-
}
69+
Ok(_) => Ok(HttpResponse::Ok().finish()),
70+
Err(_) => Ok(HttpResponse::InternalServerError().finish()),
8171
}
8272
}
8373

8474
#[post("")]
8575
async fn post_post(
8676
user: User,
87-
body: Json<BodyPost>,
88-
database_manager: web::Data<DatabaseManager>) -> Result<HttpResponse, Error> {
89-
77+
body: Json<PostData>,
78+
database_manager: web::Data<DatabaseManager>,
79+
) -> Result<HttpResponse, Error> {
9080
if !user.admin {
91-
return Ok(HttpResponse::Unauthorized().finish())
81+
return Ok(HttpResponse::Unauthorized().finish());
9282
}
9383

94-
let post = body.into_inner();
95-
96-
match database_manager.add_post(post).await {
97-
Ok(_) => {
98-
}
84+
match database_manager.add_post(body.into_inner()).await {
85+
Ok(_) => Ok(HttpResponse::Created().finish()),
9986
Err(err) => {
100-
error!("Could not post post {err}");
101-
return Ok(HttpResponse::InternalServerError().finish())
87+
error!("Could not post post: {}", err);
88+
Ok(HttpResponse::InternalServerError().finish())
10289
}
10390
}
104-
105-
Ok(HttpResponse::Ok().finish())
10691
}

src/api/users.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use actix_web::{Error, get, HttpResponse, patch, put, Scope, web, post, delete};
33
use actix_web::web::Json;
44
use futures_util::StreamExt;
55
use log::error;
6-
use crate::definitions::{BodyUser, User};
6+
use crate::definitions::{ User, UserData};
77
use crate::storage::database_manager::DatabaseManager;
88
use crate::storage::storage_manager::StorageManager;
99

@@ -93,7 +93,7 @@ async fn user_delete(
9393
#[post("")]
9494
async fn user_post(
9595
user: User,
96-
body: Json<BodyUser>,
96+
body: Json<UserData>,
9797
database_manager: web::Data<DatabaseManager>) -> Result<HttpResponse, Error> {
9898

9999
if !user.admin {
@@ -106,7 +106,7 @@ async fn user_post(
106106
Ok(_) => {
107107
}
108108
Err(err) => {
109-
error!("Could not update user {err}");
109+
error!("Could not create user {err}");
110110
return Ok(HttpResponse::InternalServerError().finish())
111111
}
112112
}
@@ -145,9 +145,10 @@ async fn user_exists(
145145
#[patch("/{userId}")]
146146
async fn user_patch(
147147
user: User,
148-
body: Json<BodyUser>,
148+
body: Json<UserData>,
149149
path: web::Path<String>,
150-
database_manager: web::Data<DatabaseManager>) -> Result<HttpResponse, Error> {
150+
database_manager: web::Data<DatabaseManager>
151+
) -> Result<HttpResponse, Error> {
151152
let user_id = path.into_inner();
152153

153154
if !user.admin && !user.compare(&user_id) {
@@ -167,7 +168,6 @@ async fn user_patch(
167168
}
168169
Some(mut modified_user) => {
169170
// Apply partial updates based on the fields provided in the request body
170-
// Can we make this a bit more clean? Especially if we have more fields in the future, this is ass to maintain
171171
if let Some(name) = body.name.clone() {
172172
modified_user.name = name;
173173
}
@@ -188,10 +188,17 @@ async fn user_patch(
188188
}
189189

190190
match database_manager.update_user(&modified_user).await {
191-
Ok(_) => {
192-
// We use the body here since I do not want to send the password back in the response
193-
//if it was not included in the request.
194-
Ok(HttpResponse::Ok().json(body))
191+
Ok(updated_user) => {
192+
// Create a new UserData instance without the password
193+
let response_data = UserData {
194+
name: updated_user.as_ref().map(|u| u.name.clone()),
195+
admin: updated_user.as_ref().map(|u| u.admin.clone()),
196+
email: updated_user.as_ref().map(|u| u.email.clone()),
197+
password: None,
198+
firstname: updated_user.as_ref().and_then(|u| u.firstname.clone()),
199+
lastname: updated_user.as_ref().and_then(|u| u.lastname.clone()),
200+
};
201+
Ok(HttpResponse::Ok().json(response_data))
195202
}
196203
Err(err) => {
197204
error!("Couldn't patch user {}", err);
@@ -202,6 +209,7 @@ async fn user_patch(
202209
}
203210
}
204211

212+
205213
#[put("/{userId}/image")]
206214
async fn user_picture_put(
207215
user: User,

src/auth.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use actix_web::dev::Payload;
66
use actix_web::web::{Data, Json};
77
use actix_web_httpauth::extractors::bearer::BearerAuth;
88
use jsonwebtoken::{encode, decode, Header as JwtHeader, Algorithm, Validation, EncodingKey, DecodingKey, errors::Result as JwtResult};
9+
use log::error;
910
use serde::{Deserialize, Serialize};
1011
use crate::definitions::{User};
1112
use crate::storage::database_manager::DatabaseManager;
@@ -68,7 +69,10 @@ impl FromRequest for User {
6869
None => Err(error::ErrorUnauthorized("User not found"))
6970
}
7071
},
71-
Err(_) => Err(error::ErrorUnauthorized("Failed to fetch user from database"))
72+
Err(err) => {
73+
error!("{}", err);
74+
Err(error::ErrorUnauthorized("Failed to fetch user from database"))
75+
}
7276
}
7377
},
7478
Err(_) => Err(error::ErrorUnauthorized("Invalid token"))
@@ -128,7 +132,10 @@ async fn auth_login(
128132
None => Err(error::ErrorUnauthorized("Invalid credentials"))
129133
}
130134
},
131-
Err(_) => Err(error::ErrorInternalServerError("Failed to fetch user from database"))
135+
Err(err) => {
136+
error!("{}", err);
137+
Err(error::ErrorUnauthorized("Failed to fetch user from database"))
138+
}
132139
}
133140
}
134141

src/definitions.rs

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::cmp::PartialEq;
22
use std::fmt;
33
use serde::{Deserialize, Deserializer, Serialize, Serializer};
4-
use surrealdb::Datetime;
5-
use surrealdb::sql::Id;
4+
use surrealdb::sql::{Datetime, Id, Thing};
65

76
#[derive(Debug, Serialize, Deserialize, Clone)]
87
pub struct IntelliThing {
@@ -21,25 +20,49 @@ impl PartialEq for IntelliThing {
2120
}
2221
}
2322

23+
#[derive(Serialize, Deserialize, Debug, Clone)]
24+
pub struct UserData {
25+
#[serde(skip_serializing_if = "Option::is_none")]
26+
#[serde(default)]
27+
pub(crate) admin: Option<bool>,
28+
pub(crate) name: Option<String>,
29+
pub(crate) email: Option<String>,
30+
#[serde(skip_serializing_if = "Option::is_none")]
31+
pub(crate) password: Option<String>,
32+
#[serde(skip_serializing_if = "Option::is_none")]
33+
pub(crate) firstname: Option<String>,
34+
#[serde(skip_serializing_if = "Option::is_none")]
35+
pub(crate) lastname: Option<String>,
36+
}
37+
2438
#[derive(Serialize, Deserialize, Debug)]
2539
pub struct User {
2640
#[serde(serialize_with = "serialize_record_id")]
2741
pub(crate) id: IntelliThing,
2842
pub(crate) admin: bool,
2943
pub(crate) name: String,
3044
pub(crate) email: String,
31-
#[serde(skip_serializing)]
3245
pub(crate) password: String,
3346
pub(crate) firstname: Option<String>,
3447
pub(crate) lastname: Option<String>,
3548
}
3649

50+
#[derive(Serialize, Deserialize, Debug)]
51+
pub struct PostData {
52+
#[serde(serialize_with = "serialize_option_record_id", deserialize_with = "deserialize_record_id")]
53+
pub(crate) author: Option<IntelliThing>,
54+
pub(crate) likes: Option<i32>,
55+
pub(crate) views: Option<i32>,
56+
pub(crate) title: Option<String>,
57+
pub(crate) posted: Option<Datetime>,
58+
}
59+
3760
#[derive(Serialize, Deserialize, Debug)]
3861
pub struct Post {
3962
#[serde(serialize_with = "serialize_record_id")]
4063
pub(crate) id: IntelliThing,
41-
#[serde(serialize_with = "serialize_record_id")]
42-
pub(crate) author: IntelliThing,
64+
#[serde(deserialize_with="deserialize_thing")]
65+
pub(crate) author: Thing,
4366
pub(crate) likes: i32,
4467
pub(crate) views: i32,
4568
pub(crate) title: String,
@@ -58,29 +81,21 @@ impl User {
5881
}
5982
}
6083

84+
impl PostData {
85+
pub fn to_surreal(&self) -> surrealdb::Result<Post> {
86+
let author = self.author.as_ref().map(|author| {
87+
Thing::from(("user", author.id.clone()))
88+
});
6189

62-
// Used by the http endpoint to allow patching the user
63-
#[derive(Serialize, Deserialize, Debug)]
64-
pub struct BodyUser {
65-
#[serde(serialize_with = "serialize_option_record_id", deserialize_with = "deserialize_record_id")]
66-
pub(crate) id: Option<IntelliThing>,
67-
pub(crate) name: Option<String>,
68-
pub(crate) email: Option<String>,
69-
pub(crate) admin: Option<bool>,
70-
pub(crate) password: Option<String>,
71-
pub(crate) firstname: Option<String>,
72-
pub(crate) lastname: Option<String>,
73-
}
74-
75-
// Used by the http endpoint to allow patching the post
76-
#[derive(Serialize, Deserialize, Debug)]
77-
pub struct BodyPost {
78-
#[serde(serialize_with = "serialize_option_record_id", deserialize_with = "deserialize_record_id")]
79-
pub(crate) author: Option<IntelliThing>,
80-
pub(crate) likes: Option<i32>,
81-
pub(crate) views: Option<i32>,
82-
pub(crate) title: Option<String>,
83-
pub(crate) posted: Option<Datetime>,
90+
Ok(Post {
91+
id: IntelliThing { id: Id::rand() }, // This will be generated by SurrealDB
92+
author: Thing::from(("user", Id::rand())),
93+
likes: self.likes.unwrap_or(0),
94+
views: self.views.unwrap_or(0),
95+
title: self.title.clone().unwrap_or_default(),
96+
posted: self.posted.clone().unwrap_or(Datetime::default()),
97+
})
98+
}
8499
}
85100

86101
fn serialize_record_id<S>(record_id: &IntelliThing, serializer: S) -> Result<S::Ok, S::Error>
@@ -90,6 +105,13 @@ fn serialize_record_id<S>(record_id: &IntelliThing, serializer: S) -> Result<S::
90105
serializer.serialize_str(&record_id.id.to_string())
91106
}
92107

108+
fn serialize_thing<S>(record_id: &Thing, serializer: S) -> Result<S::Ok, S::Error>
109+
where
110+
S: Serializer,
111+
{
112+
serializer.serialize_str(&record_id.id.to_string())
113+
}
114+
93115
fn serialize_option_record_id<S>(record_id: &Option<IntelliThing>, serializer: S) -> Result<S::Ok, S::Error>
94116
where
95117
S: Serializer,
@@ -109,4 +131,11 @@ where D: Deserializer<'de> {
109131
let buf = String::deserialize(deserializer)?;
110132

111133
Ok(Some(IntelliThing { id: Id::String(buf) }))
134+
}
135+
136+
fn deserialize_thing<'de, D>(deserializer: D) -> Result<Thing, D::Error>
137+
where D: Deserializer<'de> {
138+
let buf = String::deserialize(deserializer)?;
139+
140+
Ok(Thing::from(("", Id::from(buf))))
112141
}

0 commit comments

Comments
 (0)