diff --git a/src/api/post.rs b/src/api/post.rs new file mode 100644 index 0000000..b725461 --- /dev/null +++ b/src/api/post.rs @@ -0,0 +1,106 @@ +use std::path::PathBuf; +use actix_web::{Error, get, HttpResponse, patch, put, Scope, web, post, delete}; +use actix_web::web::Json; +use futures_util::StreamExt; +use log::error; +use crate::definitions::{BodyPost, BodyUser, Post, User}; +use crate::storage::database_manager::DatabaseManager; +use crate::storage::storage_manager::StorageManager; + +pub fn blog_service() -> Scope { + web::scope("/api/v1/posts") + .service(posts_get) + .service(post_get) + .service(post_delete) + .service(post_post) +} + +#[get("")] +async fn posts_get( + user: User, + database_manager: web::Data) -> Result { + + if !user.admin { + return Ok(HttpResponse::Unauthorized().finish()) + } + + let posts = match database_manager.fetch_posts().await { + Ok(posts) => posts, + Err(_) => { + return Ok(HttpResponse::InternalServerError().finish()); + } + }; + Ok(HttpResponse::Ok().json(posts)) +} + +#[get("/{postId}")] +async fn post_get( + path: web::Path, + database_manager: web::Data) -> Result { + + let post_id = path.into_inner(); + + let found_post: Option = match database_manager.fetch_post(post_id).await { + Ok(found_post) => { + found_post + } + Err(_) => { + return Ok(HttpResponse::InternalServerError().finish()); + } + }; + + match found_post { + Some(found_user) => { + Ok(HttpResponse::Ok().json(found_user)) + } + None => { + Ok(HttpResponse::NotFound().finish()) + } + } +} + +#[delete("/{postId}")] +async fn post_delete( + user: User, + path: web::Path, + database_manager: web::Data) -> Result { + + let post_id = path.into_inner(); + + if !user.admin { + return Ok(HttpResponse::Unauthorized().finish()) + } + + match database_manager.delete_post(post_id).await { + Ok(_) => { + Ok(HttpResponse::Ok().finish()) + }, + Err(_) => { + Ok(HttpResponse::InternalServerError().finish()) + } + } +} + +#[post("")] +async fn post_post( + user: User, + body: Json, + database_manager: web::Data) -> Result { + + if !user.admin { + return Ok(HttpResponse::Unauthorized().finish()) + } + + let post = body.into_inner(); + + match database_manager.add_post(post).await { + Ok(_) => { + } + Err(err) => { + error!("Could not post post {err}"); + return Ok(HttpResponse::InternalServerError().finish()) + } + } + + Ok(HttpResponse::Ok().finish()) +} \ No newline at end of file diff --git a/src/auth.rs b/src/auth.rs index 5f87169..7ef4ceb 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -7,7 +7,7 @@ use actix_web::web::{Data, Json}; use actix_web_httpauth::extractors::bearer::BearerAuth; use jsonwebtoken::{encode, decode, Header as JwtHeader, Algorithm, Validation, EncodingKey, DecodingKey, errors::Result as JwtResult}; use serde::{Deserialize, Serialize}; -use crate::definitions::User; +use crate::definitions::{User}; use crate::storage::database_manager::DatabaseManager; #[derive(Debug, Serialize, Deserialize)] @@ -111,7 +111,7 @@ async fn auth_login( if user.validate_password(login_credentials.password.clone()) { let iat = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as usize; let claims = Claims { - exp: iat + (24 * 60 * 60), + exp: iat + (30 * 24 * 60 * 60), iat, iss: "intelligence".to_string(), sub: user.id.to_string(), diff --git a/src/definitions.rs b/src/definitions.rs index 9062117..43f70e5 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -1,17 +1,24 @@ use std::cmp::PartialEq; use std::fmt; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use surrealdb::Datetime; use surrealdb::sql::Id; +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct IntelliThing { + pub(crate) id: Id, +} + impl fmt::Display for IntelliThing { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.id) } } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct IntelliThing { - pub(crate) id: Id, +impl PartialEq for IntelliThing { + fn eq(&self, other: &Self) -> bool { + other.id == self.id + } } #[derive(Serialize, Deserialize, Debug)] @@ -27,10 +34,16 @@ pub struct User { pub(crate) lastname: Option, } -impl PartialEq for IntelliThing { - fn eq(&self, other: &Self) -> bool { - other.id == self.id - } +#[derive(Serialize, Deserialize, Debug)] +pub struct Post { + #[serde(serialize_with = "serialize_record_id")] + pub(crate) id: IntelliThing, + #[serde(serialize_with = "serialize_record_id")] + pub(crate) author: IntelliThing, + pub(crate) likes: i32, + pub(crate) views: i32, + pub(crate) title: String, + pub(crate) posted: Datetime, } impl User { @@ -59,6 +72,16 @@ pub struct BodyUser { pub(crate) lastname: Option, } +// Used by the http endpoint to allow patching the post +#[derive(Serialize, Deserialize, Debug)] +pub struct BodyPost { + #[serde(serialize_with = "serialize_option_record_id", deserialize_with = "deserialize_record_id")] + pub(crate) author: Option, + pub(crate) likes: Option, + pub(crate) views: Option, + pub(crate) title: Option, + pub(crate) posted: Option, +} fn serialize_record_id(record_id: &IntelliThing, serializer: S) -> Result where diff --git a/src/main.rs b/src/main.rs index 67ca10d..6eae174 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey}; use crate::auth::auth_service; mod api { // Declare the 'api' module - + pub mod post; pub mod users; } @@ -64,6 +64,7 @@ async fn main() -> Result<(), Error> { .service(auth_service()) .service(api::users::user_service()) + .service(api::post::blog_service()) }) .workers(2) .bind("0.0.0.0:6969")? diff --git a/src/storage/database_manager.rs b/src/storage/database_manager.rs index a8526aa..105821b 100644 --- a/src/storage/database_manager.rs +++ b/src/storage/database_manager.rs @@ -4,7 +4,7 @@ use surrealdb::engine::remote::ws::{Client, Ws}; use surrealdb::opt::auth::{Root}; use surrealdb::{Response, Surreal}; use log::info; -use crate::definitions::{BodyUser, User}; +use crate::definitions::{BodyPost, BodyUser, IntelliThing, Post, User}; #[derive(Clone)] pub struct DatabaseManager { @@ -58,11 +58,6 @@ impl DatabaseManager { Ok(users) } - pub async fn delete_user(&self, id: String) -> surrealdb::Result> { - let deleted: Option = self.database.delete(("user", id)).await?; - Ok(deleted) - } - pub async fn fetch_user(&self, name_or_email: String) -> surrealdb::Result> { let user: Vec = self.database .query("SELECT * FROM user WHERE name = $name OR email = $name OR id = type::thing(\"user\", $name) LIMIT 1") @@ -73,6 +68,35 @@ impl DatabaseManager { Ok(user.into_iter().nth(0)) } + pub async fn fetch_posts(&self) -> surrealdb::Result> { + let posts: Vec = self.database + .query("SELECT * FROM post ORDER BY posted ASC") + .await? + .take(0)?; + + Ok(posts) + } + + pub async fn fetch_post(&self, title_or_id: String) -> surrealdb::Result> { + let post: Vec = self.database + .query("SELECT * FROM post WHERE title = $name OR id = type::thing(\"post\", $name) LIMIT 1") + .bind(("name", title_or_id)) + .await? + .take(0)?; + + Ok(post.into_iter().nth(0)) + } + + pub async fn delete_user(&self, id: String) -> surrealdb::Result> { + let deleted: Option = self.database.delete(("user", id)).await?; + Ok(deleted) + } + + pub async fn delete_post(&self, id: String) -> surrealdb::Result> { + let deleted: Option = self.database.delete(("post", id)).await?; + Ok(deleted) + } + pub async fn add_user(&self, user: BodyUser) -> surrealdb::Result> { self.database .insert("user") @@ -80,6 +104,13 @@ impl DatabaseManager { .await } + pub async fn add_post(&self, post: BodyPost) -> surrealdb::Result> { + self.database + .insert("post") + .content(post) + .await + } + pub async fn update_user(&self, user: &User) -> surrealdb::Result> { self.database .update(("user", user.id.to_string()))