WIP - ActixWeb multi-tenant blog and newsletter API server. Originally forked from LukeMathWalker/zero-to-production.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Adds endpoint allowing user to update profile

+93 -2
+37 -2
src/routes/admin/user/index.rs
··· 1 1 use crate::authentication::UserId; 2 2 use crate::models::{UserProfile, UserProfileAPI}; 3 - use crate::utils::e404; 3 + use crate::utils::{e400, e404, e500}; 4 4 use actix_web::http::header::ContentType; 5 - use actix_web::{HttpResponse, get, web}; 5 + use actix_web::{HttpResponse, get, put, web}; 6 6 use anyhow::Context; 7 + use serde::Deserialize; 7 8 use sqlx::PgPool; 8 9 9 10 #[get("/user")] ··· 22 23 .content_type(ContentType::json()) 23 24 .json(user)) 24 25 } 26 + 27 + #[derive(Deserialize)] 28 + struct UserProfileParams { 29 + pub bio: String, 30 + pub description: String, 31 + pub display_name: String, 32 + } 33 + 34 + #[put("/user")] 35 + #[tracing::instrument( 36 + name = "Updating user profile", 37 + skip_all, 38 + fields(user_id=%*user_id) 39 + )] 40 + pub async fn put( 41 + params: web::Json<UserProfileParams>, 42 + pool: web::Data<PgPool>, 43 + user_id: web::ReqData<UserId>, 44 + ) -> Result<HttpResponse, actix_web::Error> { 45 + UserProfile { 46 + bio: params.0.bio, 47 + description: params.0.description, 48 + display_name: params.0.display_name, 49 + user_id: *user_id.into_inner(), 50 + } 51 + .validate() 52 + .map_err(e400)? 53 + .update(&pool) 54 + .await 55 + .context("Failed to update user profile.") 56 + .map_err(e500)?; 57 + 58 + Ok(HttpResponse::Ok().finish()) 59 + }
+1
src/startup.rs
··· 97 97 .service(admin::newsletters::detail::put) 98 98 .service(admin::newsletters::detail::publish::put) 99 99 .service(admin::user::get) 100 + .service(admin::user::put) 100 101 .service(admin::password::put), 101 102 ) 102 103 .service(health_check::get)
+43
tests/api/admin/user/index.rs
··· 20 20 let response_body: UserProfileAPI = user_response.json().await.unwrap(); 21 21 assert_eq!(app.test_user.username, response_body.username); 22 22 } 23 + 24 + #[tokio::test] 25 + async fn authenticated_user_can_update_profile() { 26 + let app = spawn_app().await; 27 + app.test_user.login(&app).await; 28 + 29 + let response = app.get_admin_user().await; 30 + let response_body: UserProfileAPI = response.json().await.unwrap(); 31 + 32 + assert_eq!(response_body.bio, ""); 33 + assert_eq!(response_body.description, ""); 34 + assert_eq!(response_body.display_name, ""); 35 + 36 + let response = app 37 + .put_admin_update_user(&serde_json::json!({ 38 + "bio": "my bio", 39 + "description": "my description", 40 + "display_name": "my display name", 41 + })) 42 + .await; 43 + assert_eq!(200, response.status().as_u16()); 44 + 45 + let response = app.get_admin_user().await; 46 + let response_body: UserProfileAPI = response.json().await.unwrap(); 47 + 48 + assert_eq!(response_body.bio, "my bio".to_string()); 49 + assert_eq!(response_body.description, "my description".to_string()); 50 + assert_eq!(response_body.display_name, "my display name".to_string()); 51 + } 52 + 53 + #[tokio::test] 54 + async fn unauthenticated_user_cannot_update_profile() { 55 + let app = spawn_app().await; 56 + let response = app 57 + .put_admin_update_user(&serde_json::json!({ 58 + "bio": "my bio", 59 + "description": "my description", 60 + "display_name": "my display name" 61 + })) 62 + .await; 63 + 64 + assert_eq!(401, response.status().as_u16()); 65 + }
+12
tests/api/helpers.rs
··· 188 188 .expect("Failed to execute request.") 189 189 } 190 190 191 + pub async fn put_admin_update_user<Body>(&self, body: &Body) -> reqwest::Response 192 + where 193 + Body: serde::Serialize, 194 + { 195 + self.api_client 196 + .put(&format!("{}/admin/user", &self.address)) 197 + .json(body) 198 + .send() 199 + .await 200 + .expect("Failed to execute request.") 201 + } 202 + 191 203 pub async fn get_authenticate(&self) -> reqwest::Response { 192 204 self.api_client 193 205 .get(&format!("{}/admin/authenticate", &self.address))