diff --git a/src/domain/user/api.rs b/src/domain/user/api.rs new file mode 100644 index 0000000..8e827a1 --- /dev/null +++ b/src/domain/user/api.rs @@ -0,0 +1,53 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::{Email, Handle, User}; + +#[derive(Deserialize, Serialize)] +pub struct GetUserResponse { + id: Uuid, + email: Email, + handle: Handle, + name: Option, + profile_photo: Option, + bio: Option, +} + +impl GetUserResponse { + pub fn id(&self) -> Uuid { + self.id + } + + pub fn email(&self) -> Email { + self.email.clone() + } + + pub fn handle(&self) -> Handle { + self.handle.clone() + } + + pub fn name(&self) -> Option { + self.name.clone() + } + + pub fn profile_photo(&self) -> Option { + self.profile_photo.clone() + } + + pub fn bio(&self) -> Option { + self.bio.clone() + } +} + +impl From for GetUserResponse { + fn from(user: User) -> Self { + GetUserResponse { + id: user.id, + email: user.email, + handle: user.handle, + name: user.name, + profile_photo: user.profile_photo, + bio: user.bio, + } + } +} diff --git a/src/domain/user/mod.rs b/src/domain/user/mod.rs index 06d98cf..88f29cf 100644 --- a/src/domain/user/mod.rs +++ b/src/domain/user/mod.rs @@ -1,7 +1,9 @@ +mod api; mod credentials; mod tests; use actix_web::HttpResponse; +pub use api::GetUserResponse; pub use credentials::{ deserialize_handle_option, deserialize_password_option, deserilaize_email_option, Email, EmailValidationErr, Handle, HandleValidationErr, Login, Password, PasswordValidationErr, diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 16ea71a..c3ebc00 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,4 +1,4 @@ pub mod health_check; pub mod middleware; pub mod server; -pub mod user; \ No newline at end of file +pub mod user; diff --git a/src/handlers/user/get.rs b/src/handlers/user/get.rs new file mode 100644 index 0000000..f5ab7d9 --- /dev/null +++ b/src/handlers/user/get.rs @@ -0,0 +1,52 @@ +use actix_web::{ + web::{Data, Path}, + HttpResponse, +}; +use sqlx::PgPool; +use uuid::Uuid; + +use crate::{domain::user::GetUserResponse, storage}; + +#[tracing::instrument( + name = "Getting user by ID", + skip(user_id, db_pool), + fields( + id = %user_id, + ), +)] +pub async fn get_by_id(user_id: Path, db_pool: Data) -> HttpResponse { + let id = user_id.into_inner(); + + match storage::get_user_by_id(&db_pool, id).await { + Ok(user) => { + if user.deleted_at().is_some() { + let err = format!("user {} has been soft deleted", id); + tracing::error!(err); + HttpResponse::BadRequest().body(err) + } else { + let response: GetUserResponse = user.into(); + let response_str = match serde_json::to_string(&response) { + Ok(response_str) => response_str, + Err(e) => { + let err = format!("failed to parse user struct {} to json: {:?}", id, e); + tracing::error!(err); + return HttpResponse::InternalServerError().finish(); + } + }; + HttpResponse::Ok().body(response_str) + } + } + Err(e) => match e { + sqlx::Error::RowNotFound => { + let err = format!("user {} not found", id); + tracing::error!(err); + HttpResponse::NotFound().body(err) + } + e => { + let err = format!("failed to get user {}: {:?}", id, e); + tracing::error!(err); + HttpResponse::InternalServerError().finish() + } + }, + } +} diff --git a/src/handlers/user/mod.rs b/src/handlers/user/mod.rs index 6a97238..db97702 100644 --- a/src/handlers/user/mod.rs +++ b/src/handlers/user/mod.rs @@ -1,11 +1,13 @@ mod confirm; mod delete; +mod get; mod login; mod signup; mod update; pub use confirm::*; pub use delete::*; +pub use get::*; pub use login::*; pub use signup::*; pub use update::*; diff --git a/src/startup.rs b/src/startup.rs index 880a971..be47e3a 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -3,7 +3,6 @@ use crate::{ domain::{email, user::Email}, handlers::{ health_check::{health_check, HEALTH_CHECK_PATH}, - middleware::AuthMiddleware, server, user, }, }; @@ -70,6 +69,7 @@ impl App { ) .service( scope("/{user_id}") + .route("", get().to(user::get_by_id)) .route("", put().to(user::update)) .route("", patch().to(user::patch)) .route("", delete().to(user::soft_delete)) diff --git a/tests/api/user/get.rs b/tests/api/user/get.rs new file mode 100644 index 0000000..c5e11c7 --- /dev/null +++ b/tests/api/user/get.rs @@ -0,0 +1,86 @@ +use claim::assert_ok; +use fake::{faker::internet::en::SafeEmail, Fake}; +use muttr_server::{ + domain::user::{self, GetUserResponse}, + handlers::user::BASE_PATH, +}; +use uuid::Uuid; + +use crate::utils::{ + app::TestApp, + http_client::{ContentType, Header, Path}, +}; + +#[actix::test] +async fn test_get_by_id_success() { + let mut app = TestApp::spawn().await; + + for x in 1..6 { + let email = assert_ok!( + user::Email::try_from(SafeEmail().fake::()), + "failed to generated valid user email" + ); + let handle = format!("test.user{}", x); + let user = app + .database + .insert_user(email.as_ref(), &handle, true) + .await; + + let response = app + .client + .request( + Path::GET(format!("{}/{}", BASE_PATH, user.id())), + &[Header::ContentType(ContentType::Json)], + None::, + ) + .await; + + assert_eq!( + 200, + response.status(), + "The API did not return 200 on valid get user by id request: {}", + response.text().await.unwrap_or_default(), + ); + + let user_res = match response.json::().await { + Ok(json) => json, + Err(e) => panic!( + "failed to unmarshal json from api response into GetUserResponse struct: {:?}", + e + ), + }; + + assert_eq!(user.id(), user_res.id(), "id does not match",); + assert_eq!(user.email(), user_res.email(), "email does not match",); + assert_eq!(user.handle(), user_res.handle(), "handle does not match",); + assert_eq!(user.name(), user_res.name(), "name does not match",); + assert_eq!( + user.profile_photo(), + user_res.profile_photo(), + "profile_photo does not match", + ); + assert_eq!(user.bio(), user_res.bio(), "bio does not match",); + } +} + +#[actix::test] +async fn test_get_by_id_failure() { + let app = TestApp::spawn().await; + + let id = Uuid::new_v4(); + + let user_res = app + .client + .request( + Path::GET(format!("{}/{}", BASE_PATH, id)), + &[Header::ContentType(ContentType::Json)], + None::, + ) + .await; + + assert_eq!( + 404, + user_res.status(), + "The API did not return 404 when trying to GET a non-existant user id", + ); +} diff --git a/tests/api/user/mod.rs b/tests/api/user/mod.rs index 7217032..6255cfe 100644 --- a/tests/api/user/mod.rs +++ b/tests/api/user/mod.rs @@ -1,5 +1,6 @@ mod confirm; mod delete; +mod get; mod login; mod patch; mod signup;