/src/app/users.rs

https://github.com/fairingrey/actix-realworld-example-app · Rust · 191 lines · 166 code · 22 blank · 3 comment · 4 complexity · 505a04dad855e69e73d6ba458468b08f MD5 · raw file

  1. use actix_web::{HttpRequest, HttpResponse, web::Json, ResponseError, web::Data};
  2. use futures::{future::result, Future};
  3. use regex::Regex;
  4. use std::convert::From;
  5. use validator::Validate;
  6. use super::AppState;
  7. use crate::models::User;
  8. use crate::prelude::*;
  9. use crate::utils::{
  10. auth::{authenticate, Auth},
  11. jwt::CanGenerateJwt,
  12. };
  13. lazy_static! {
  14. static ref RE_USERNAME: Regex = Regex::new(r"^[_0-9a-zA-Z]+$").unwrap();
  15. }
  16. #[derive(Debug, Deserialize)]
  17. pub struct In<U> {
  18. user: U,
  19. }
  20. // Client Messages ↓
  21. #[derive(Debug, Validate, Deserialize)]
  22. pub struct RegisterUser {
  23. #[validate(
  24. length(
  25. min = "1",
  26. max = "20",
  27. message = "fails validation - must be 1-20 characters long"
  28. ),
  29. regex(
  30. path = "RE_USERNAME",
  31. message = "fails validation - is not only alphanumeric/underscore characters"
  32. )
  33. )]
  34. pub username: String,
  35. #[validate(email(message = "fails validation - is not a valid email address"))]
  36. pub email: String,
  37. #[validate(length(
  38. min = "8",
  39. max = "72",
  40. message = "fails validation - must be 8-72 characters long"
  41. ))]
  42. pub password: String,
  43. }
  44. #[derive(Debug, Validate, Deserialize)]
  45. pub struct LoginUser {
  46. #[validate(email(message = "fails validation - is not a valid email address"))]
  47. pub email: String,
  48. #[validate(length(
  49. min = "8",
  50. max = "72",
  51. message = "fails validation - must be 8-72 characters long"
  52. ))]
  53. pub password: String,
  54. }
  55. #[derive(Debug, Validate, Deserialize)]
  56. pub struct UpdateUser {
  57. #[validate(
  58. length(
  59. min = "1",
  60. max = "20",
  61. message = "fails validation - must be 1-20 characters long"
  62. ),
  63. regex(
  64. path = "RE_USERNAME",
  65. message = "fails validation - is not only alphanumeric/underscore characters"
  66. )
  67. )]
  68. pub username: Option<String>,
  69. #[validate(email)]
  70. pub email: Option<String>,
  71. #[validate(length(
  72. min = "8",
  73. max = "72",
  74. message = "fails validation - must be 8-72 characters long"
  75. ))]
  76. pub password: Option<String>,
  77. #[validate(length(min = "1", message = "fails validation - cannot be empty"))]
  78. pub bio: Option<String>,
  79. #[validate(url(message = "is not a URL"))]
  80. pub image: Option<String>,
  81. }
  82. #[derive(Debug)]
  83. pub struct UpdateUserOuter {
  84. pub auth: Auth,
  85. pub update_user: UpdateUser,
  86. }
  87. // JSON response objects ↓
  88. #[derive(Debug, Serialize)]
  89. pub struct UserResponse {
  90. pub user: UserResponseInner,
  91. }
  92. #[derive(Debug, Serialize)]
  93. pub struct UserResponseInner {
  94. pub email: String,
  95. pub token: String,
  96. pub username: String,
  97. pub bio: Option<String>,
  98. pub image: Option<String>,
  99. }
  100. impl From<User> for UserResponse {
  101. fn from(user: User) -> Self {
  102. UserResponse {
  103. user: UserResponseInner {
  104. token: user.generate_jwt().unwrap(),
  105. email: user.email,
  106. username: user.username,
  107. bio: user.bio,
  108. image: user.image,
  109. },
  110. }
  111. }
  112. }
  113. impl UserResponse {
  114. fn create_with_auth(auth: Auth) -> Self {
  115. UserResponse {
  116. user: UserResponseInner {
  117. token: auth.token,
  118. email: auth.user.email,
  119. username: auth.user.username,
  120. bio: auth.user.bio,
  121. image: auth.user.image,
  122. },
  123. }
  124. }
  125. }
  126. // Route handlers ↓
  127. pub fn register(
  128. (form, state): (Json<In<RegisterUser>>, Data<AppState>),
  129. ) -> impl Future<Item = HttpResponse, Error = Error> {
  130. let register_user = form.into_inner().user;
  131. result(register_user.validate())
  132. .from_err()
  133. .and_then(move |_| state.db.send(register_user).from_err())
  134. .and_then(|res| match res {
  135. Ok(res) => Ok(HttpResponse::Ok().json(res)),
  136. Err(e) => Ok(e.error_response()),
  137. })
  138. }
  139. pub fn login(
  140. (form, state): (Json<In<LoginUser>>, Data<AppState>),
  141. ) -> impl Future<Item = HttpResponse, Error = Error> {
  142. let login_user = form.into_inner().user;
  143. result(login_user.validate())
  144. .from_err()
  145. .and_then(move |_| state.db.send(login_user).from_err())
  146. .and_then(|res| match res {
  147. Ok(res) => Ok(HttpResponse::Ok().json(res)),
  148. Err(e) => Ok(e.error_response()),
  149. })
  150. }
  151. pub fn get_current(state: Data<AppState>, req: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> {
  152. authenticate(&state, &req)
  153. .and_then(|auth| Ok(HttpResponse::Ok().json(UserResponse::create_with_auth(auth))))
  154. }
  155. pub fn update(
  156. state: Data<AppState>,
  157. (form, req): (Json<In<UpdateUser>>, HttpRequest),
  158. ) -> impl Future<Item = HttpResponse, Error = Error> {
  159. let update_user = form.into_inner().user;
  160. let db = state.db.clone();
  161. result(update_user.validate())
  162. .from_err()
  163. .and_then(move |_| authenticate(&state, &req))
  164. .and_then(move |auth| db.send(UpdateUserOuter { auth, update_user }).from_err())
  165. .and_then(|res| match res {
  166. Ok(res) => Ok(HttpResponse::Ok().json(res)),
  167. Err(e) => Ok(e.error_response()),
  168. })
  169. }