diff options
Diffstat (limited to 'src/types')
-rw-r--r-- | src/types/extract.rs | 65 | ||||
-rw-r--r-- | src/types/mod.rs | 5 | ||||
-rw-r--r-- | src/types/post.rs | 83 | ||||
-rw-r--r-- | src/types/response.rs | 60 | ||||
-rw-r--r-- | src/types/session.rs | 38 | ||||
-rw-r--r-- | src/types/user.rs | 79 |
6 files changed, 330 insertions, 0 deletions
diff --git a/src/types/extract.rs b/src/types/extract.rs new file mode 100644 index 0000000..6518ca1 --- /dev/null +++ b/src/types/extract.rs @@ -0,0 +1,65 @@ +use std::io::Read; + +use axum::{extract::{FromRequestParts, FromRequest}, async_trait, response::Response, http::{request::Parts, Request}, TypedHeader, headers::Cookie, body::HttpBody, BoxError}; +use bytes::Bytes; +use serde::de::DeserializeOwned; + +use crate::types::{user::User, response::{ResponseCode, Result}, session::Session}; + +pub struct AuthorizedUser(pub User); + +#[async_trait] +impl<S> FromRequestParts<S> for AuthorizedUser where S: Send + Sync { + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> { + + let Ok(Some(cookies)) = Option::<TypedHeader<Cookie>>::from_request_parts(parts, state).await else { + return Err(ResponseCode::Forbidden.msg("No cookies provided")) + }; + + let Some(token) = cookies.get("auth") else { + return Err(ResponseCode::Forbidden.msg("No auth token provided")) + }; + + let Ok(session) = Session::from_token(&token) else { + return Err(ResponseCode::Unauthorized.msg("Auth token invalid")) + }; + + let Ok(user) = User::from_user_id(session.user_id, true) else { + return Err(ResponseCode::InternalServerError.msg("Valid token but no valid user")) + }; + + Ok(AuthorizedUser(user)) + } +} + +pub struct Json<T>(pub T); + +#[async_trait] +impl<T, S, B> FromRequest<S, B> for Json<T> where + T: DeserializeOwned, + B: HttpBody + Send + 'static, + B::Data: Send, + B::Error: Into<BoxError>, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request(req: Request<B>, state: &S) -> Result<Self> { + + let Ok(bytes) = Bytes::from_request(req, state).await else { + return Err(ResponseCode::InternalServerError.msg("Failed to read request body")); + }; + + let Ok(string) = String::from_utf8(bytes.bytes().flatten().collect()) else { + return Err(ResponseCode::BadRequest.msg("Invalid utf8 body")) + }; + + let Ok(value) = serde_json::from_str(&string) else { + return Err(ResponseCode::BadRequest.msg("Invalid request body")) + }; + + Ok(Json(value)) + } +}
\ No newline at end of file diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..089885e --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,5 @@ +pub mod user; +pub mod post; +pub mod session; +pub mod extract; +pub mod response;
\ No newline at end of file diff --git a/src/types/post.rs b/src/types/post.rs new file mode 100644 index 0000000..94f0a9e --- /dev/null +++ b/src/types/post.rs @@ -0,0 +1,83 @@ +use std::collections::HashSet; +use serde::Serialize; + +use crate::database; +use crate::types::response::{Result, ResponseCode}; + +#[derive(Serialize)] +pub struct Post { + pub post_id: u64, + pub user_id: u64, + pub content: String, + pub likes: HashSet<u64>, + pub comments: Vec<(u64, String)>, + pub date: u64 +} + +impl Post { + + pub fn from_post_id(post_id: u64) -> Result<Self> { + let Ok(Some(post)) = database::posts::get_post(post_id) else { + return Err(ResponseCode::BadRequest.msg("Post does not exist")) + }; + + Ok(post) + } + + // pub fn from_post_ids(post_ids: Vec<u64>) -> Vec<Self> { + // post_ids.iter().map(|id| { + // let Ok(post) = Post::from_post_id(*id) else { + // return None; + // }; + // Some(post) + // }).flatten().collect() + // } + + pub fn from_post_page(page: u64) -> Result<Vec<Self>> { + let Ok(posts) = database::posts::get_post_page(page) else { + return Err(ResponseCode::BadRequest.msg("Failed to fetch posts")) + }; + Ok(posts) + } + + pub fn from_user_id(user_id: u64) -> Result<Vec<Self>> { + let Ok(posts) = database::posts::get_users_posts(user_id) else { + return Err(ResponseCode::BadRequest.msg("Failed to fetch posts")) + }; + Ok(posts) + } + + pub fn new(user_id: u64, content: String) -> Result<Self> { + let Ok(post) = database::posts::add_post(user_id, &content) else { + return Err(ResponseCode::InternalServerError.msg("Failed to create post")) + }; + + Ok(post) + } + + pub fn comment(&mut self, user_id: u64, content: String) -> Result<()> { + self.comments.push((user_id, content)); + + if database::posts::update_post(self.post_id, &self.likes, &self.comments).is_err() { + return Err(ResponseCode::InternalServerError.msg("Failed to comment on post")) + } + + Ok(()) + } + + pub fn like(&mut self, user_id: u64, state: bool) -> Result<()> { + + if state { + self.likes.insert(user_id); + } else { + self.likes.remove(&user_id); + } + + if database::posts::update_post(self.post_id, &self.likes, &self.comments).is_err() { + return Err(ResponseCode::InternalServerError.msg("Failed to comment on post")) + } + + Ok(()) + } + +}
\ No newline at end of file diff --git a/src/types/response.rs b/src/types/response.rs new file mode 100644 index 0000000..bea3406 --- /dev/null +++ b/src/types/response.rs @@ -0,0 +1,60 @@ +use axum::{response::{IntoResponse, Response}, http::{StatusCode, Request, HeaderValue}, body::Body, headers::HeaderName}; +use tower::ServiceExt; +use tower_http::services::ServeFile; + +#[derive(Debug)] +pub enum ResponseCode { + Success, + Created, + BadRequest, + Unauthorized, + Forbidden, + NotFound, + ImATeapot, + InternalServerError +} + +impl ResponseCode { + pub fn code(self) -> StatusCode { + match self { + Self::Success => StatusCode::OK, + Self::Created => StatusCode::CREATED, + Self::BadRequest => StatusCode::BAD_REQUEST, + Self::Unauthorized => StatusCode::UNAUTHORIZED, + Self::Forbidden => StatusCode::FORBIDDEN, + Self::NotFound => StatusCode::NOT_FOUND, + Self::ImATeapot => StatusCode::IM_A_TEAPOT, + Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR + } + } + + pub fn msg(self, msg: &str) -> Response { + (self.code(), msg.to_owned()).into_response() + } + + pub fn json(self, json: &str) -> Response { + let mut res = (self.code(), json.to_owned()).into_response(); + res.headers_mut().insert( + HeaderName::from_static("content-type"), HeaderValue::from_static("application/json"), + ); + res + } + + pub async fn file(self, path: &str) -> Result<Response> { + if path.chars().position(|c| c == '.' ).is_none() { + return Err(ResponseCode::BadRequest.msg("Folders cannot be served")); + } + let path = format!("public{}", path); + let svc = ServeFile::new(path); + let Ok(mut res) = svc.oneshot(Request::new(Body::empty())).await else { + return Err(ResponseCode::InternalServerError.msg("Error wile fetching file")); + }; + if res.status() != StatusCode::OK { + return Err(ResponseCode::NotFound.msg("File not found")); + } + *res.status_mut() = self.code(); + Ok(res.into_response()) + } +} + +pub type Result<T> = std::result::Result<T, Response>;
\ No newline at end of file diff --git a/src/types/session.rs b/src/types/session.rs new file mode 100644 index 0000000..8064fb1 --- /dev/null +++ b/src/types/session.rs @@ -0,0 +1,38 @@ +use rand::{distributions::Alphanumeric, Rng}; +use serde::Serialize; + +use crate::database; +use crate::types::response::{Result, ResponseCode}; + +#[derive(Serialize)] +pub struct Session { + pub user_id: u64, + pub token: String +} + +impl Session { + + pub fn from_token(token: &str) -> Result<Self> { + let Ok(Some(session)) = database::sessions::get_session(token) else { + return Err(ResponseCode::BadRequest.msg("Invalid auth token")); + }; + + Ok(session) + } + + pub fn new(user_id: u64) -> Result<Self> { + let token: String = rand::thread_rng().sample_iter(&Alphanumeric).take(32).map(char::from).collect(); + match database::sessions::set_session(user_id, &token) { + Err(_) => return Err(ResponseCode::BadRequest.msg("Failed to create session")), + Ok(_) => return Ok(Session {user_id, token}) + }; + } + + pub fn delete(user_id: u64) -> Result<()> { + if let Err(_) = database::sessions::delete_session(user_id) { + return Err(ResponseCode::InternalServerError.msg("Failed to logout")); + }; + Ok(()) + } + +}
\ No newline at end of file diff --git a/src/types/user.rs b/src/types/user.rs new file mode 100644 index 0000000..1213a75 --- /dev/null +++ b/src/types/user.rs @@ -0,0 +1,79 @@ +use serde::{Serialize, Deserialize}; + +use crate::database; +use crate::types::response::{Result, ResponseCode}; + + +#[derive(Serialize, Deserialize, Debug)] +pub struct User { + pub user_id: u64, + pub firstname: String, + pub lastname: String, + pub email: String, + pub password: String, + pub gender: String, + pub date: u64, + pub day: u8, + pub month: u8, + pub year: u32, +} + +impl User { + + pub fn from_user_id(user_id: u64, hide_password: bool) -> Result<Self> { + let Ok(Some(user)) = database::users::get_user_by_id(user_id, hide_password) else { + return Err(ResponseCode::BadRequest.msg("User does not exist")) + }; + + Ok(user) + } + + pub fn from_user_ids(user_ids: Vec<u64>) -> Vec<Self> { + user_ids.iter().map(|user_id| { + let Ok(Some(user)) = database::users::get_user_by_id(*user_id, true) else { + return None; + }; + Some(user) + }).flatten().collect() + } + + pub fn from_user_page(page: u64) -> Result<Vec<Self>> { + let Ok(users) = database::users::get_user_page(page, true) else { + return Err(ResponseCode::BadRequest.msg("Failed to fetch users")) + }; + Ok(users) + } + + pub fn from_email(email: &str) -> Result<Self> { + let Ok(Some(user)) = database::users::get_user_by_email(email, false) else { + return Err(ResponseCode::BadRequest.msg("User does not exist")) + }; + + Ok(user) + } + + pub fn from_password(password: &str) -> Result<Self> { + let Ok(Some(user)) = database::users::get_user_by_password(password, true) else { + return Err(ResponseCode::BadRequest.msg("User does not exist")) + }; + + Ok(user) + } + + pub fn new(firstname: String, lastname: String, email: String, password: String, gender: String, day: u8, month: u8, year: u32) -> Result<Self> { + if let Ok(_) = User::from_email(&email) { + return Err(ResponseCode::BadRequest.msg(&format!("Email is already in use by {}", &email))) + } + + if let Ok(user) = User::from_password(&password) { + return Err(ResponseCode::BadRequest.msg(&format!("Password is already in use by {}", user.email))) + } + + let Ok(user) = database::users::add_user(&firstname, &lastname, &email, &password, &gender, day, month, year) else { + return Err(ResponseCode::InternalServerError.msg("Failed to create new uesr")) + }; + + Ok(user) + } + +}
\ No newline at end of file |