Przeglądaj źródła

Separate each route to its own file.

Lee Morgan 1 miesiąc temu
rodzic
commit
67bce3d099

+ 4 - 4
src/main.rs

@@ -49,10 +49,10 @@ async fn main() -> std::io::Result<()> {
             .app_data(web::Data::new(pool.clone()))
             .app_data(config.clone())
             .service(index)
-            .service(routes::users::create_user)
-            .service(routes::users::login)
-            .service(routes::users::logout)
-            .service(routes::users::get_user)
+            .service(routes::users::create::create_user)
+            .service(routes::users::login::login)
+            .service(routes::users::logout::logout)
+            .service(routes::users::get::get_user)
     })
     .bind((host.as_str(), port))?
     .run()

+ 0 - 161
src/routes/users.rs

@@ -1,161 +0,0 @@
-use actix_web::cookie::{Cookie, SameSite};
-use actix_web::{get, post, web, HttpResponse};
-use argon2::{
-    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
-    Argon2,
-};
-use jsonwebtoken::{encode, EncodingKey, Header};
-use serde::Deserialize;
-use serde_json::json;
-use sqlx::PgPool;
-use std::time::{SystemTime, UNIX_EPOCH};
-use validator::Validate;
-
-use crate::auth::{AuthUser, Claims};
-use crate::models::user::User;
-use crate::Config;
-
-#[derive(Deserialize, Validate)]
-pub struct CreateUserInput {
-    pub name: String,
-    #[validate(email(message = "Invalid email address"))]
-    pub email: String,
-    pub password: String,
-    pub confirm_password: String,
-}
-
-#[post("/user")]
-pub async fn create_user(
-    pool: web::Data<PgPool>,
-    body: web::Json<CreateUserInput>,
-) -> HttpResponse {
-    if body.validate().is_err() {
-        return HttpResponse::BadRequest().json(json!({"error": "Invalid email address"}));
-    }
-
-    if body.password != body.confirm_password {
-        return HttpResponse::BadRequest().json(json!({"error": "Passwords do not match"}));
-    }
-
-    if body.password.chars().count() < 12 {
-        return HttpResponse::BadRequest()
-            .json(json!({"error": "Password must be at least 12 characters"}));
-    }
-
-    let email = body.email.to_lowercase();
-
-    let salt = SaltString::generate(&mut OsRng);
-    let pass_hash = match Argon2::default().hash_password(body.password.as_bytes(), &salt) {
-        Ok(h) => h.to_string(),
-        Err(_) => {
-            return HttpResponse::InternalServerError()
-                .json(json!({"error": "Failed to hash password"}))
-        }
-    };
-
-    match User::create(pool.get_ref(), &body.name, &email, &pass_hash).await {
-        Ok(_) => HttpResponse::Created().json(json!({"success": true})),
-        Err(sqlx::Error::Database(e)) if e.code().as_deref() == Some("23505") => {
-            HttpResponse::Conflict().json(json!({"error": "Email already in use"}))
-        }
-        Err(_) => {
-            HttpResponse::InternalServerError().json(json!({"error": "Failed to create user"}))
-        }
-    }
-}
-
-#[derive(Deserialize)]
-pub struct LoginInput {
-    pub email: String,
-    pub password: String,
-}
-
-#[post("/user/login")]
-pub async fn login(
-    pool: web::Data<PgPool>,
-    config: web::Data<Config>,
-    body: web::Json<LoginInput>,
-) -> HttpResponse {
-    let email = body.email.to_lowercase();
-
-    let user = match User::find_by_email(pool.get_ref(), &email).await {
-        Ok(Some(u)) => u,
-        Ok(None) => {
-            return HttpResponse::Unauthorized()
-                .json(json!({"error": "Invalid email or password"}))
-        }
-        Err(_) => {
-            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
-        }
-    };
-
-    let parsed_hash = match PasswordHash::new(&user.pass_hash) {
-        Ok(h) => h,
-        Err(_) => {
-            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
-        }
-    };
-
-    if Argon2::default()
-        .verify_password(body.password.as_bytes(), &parsed_hash)
-        .is_err()
-    {
-        return HttpResponse::Unauthorized().json(json!({"error": "Invalid email or password"}));
-    }
-
-    let exp = (SystemTime::now()
-        .duration_since(UNIX_EPOCH)
-        .unwrap()
-        .as_secs()
-        + 7 * 24 * 60 * 60) as usize;
-
-    let claims = Claims {
-        id: user.id.to_string(),
-        uuid_key: user.uuid_key.to_string(),
-        exp,
-    };
-
-    let token = match encode(
-        &Header::default(),
-        &claims,
-        &EncodingKey::from_secret(config.jwt_secret.as_bytes()),
-    ) {
-        Ok(t) => t,
-        Err(_) => {
-            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
-        }
-    };
-
-    let cookie = Cookie::build("token", token)
-        .http_only(true)
-        .secure(config.is_production)
-        .same_site(SameSite::Strict)
-        .path("/")
-        .max_age(actix_web::cookie::time::Duration::days(7))
-        .finish();
-
-    HttpResponse::Ok().cookie(cookie).json(json!({"success": true}))
-}
-
-#[get("/user")]
-pub async fn get_user(auth: AuthUser) -> HttpResponse {
-    let user = auth.0;
-    HttpResponse::Ok().json(json!({
-        "id": user.id,
-        "name": user.name,
-        "email": user.email,
-    }))
-}
-
-#[post("/user/logout")]
-pub async fn logout(config: web::Data<Config>) -> HttpResponse {
-    let cookie = Cookie::build("token", "")
-        .http_only(true)
-        .secure(config.is_production)
-        .same_site(SameSite::Strict)
-        .path("/")
-        .max_age(actix_web::cookie::time::Duration::ZERO)
-        .finish();
-
-    HttpResponse::Ok().cookie(cookie).json(json!({"success": true}))
-}

+ 60 - 0
src/routes/users/create.rs

@@ -0,0 +1,60 @@
+use actix_web::{post, web, HttpResponse};
+use argon2::{
+    password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
+    Argon2,
+};
+use serde::Deserialize;
+use serde_json::json;
+use sqlx::PgPool;
+use validator::Validate;
+
+use crate::models::user::User;
+
+#[derive(Deserialize, Validate)]
+pub struct CreateUserInput {
+    pub name: String,
+    #[validate(email(message = "Invalid email address"))]
+    pub email: String,
+    pub password: String,
+    pub confirm_password: String,
+}
+
+#[post("/user")]
+pub async fn create_user(
+    pool: web::Data<PgPool>,
+    body: web::Json<CreateUserInput>,
+) -> HttpResponse {
+    if body.validate().is_err() {
+        return HttpResponse::BadRequest().json(json!({"error": "Invalid email address"}));
+    }
+
+    if body.password != body.confirm_password {
+        return HttpResponse::BadRequest().json(json!({"error": "Passwords do not match"}));
+    }
+
+    if body.password.chars().count() < 12 {
+        return HttpResponse::BadRequest()
+            .json(json!({"error": "Password must be at least 12 characters"}));
+    }
+
+    let email = body.email.to_lowercase();
+
+    let salt = SaltString::generate(&mut OsRng);
+    let pass_hash = match Argon2::default().hash_password(body.password.as_bytes(), &salt) {
+        Ok(h) => h.to_string(),
+        Err(_) => {
+            return HttpResponse::InternalServerError()
+                .json(json!({"error": "Failed to hash password"}))
+        }
+    };
+
+    match User::create(pool.get_ref(), &body.name, &email, &pass_hash).await {
+        Ok(_) => HttpResponse::Created().json(json!({"success": true})),
+        Err(sqlx::Error::Database(e)) if e.code().as_deref() == Some("23505") => {
+            HttpResponse::Conflict().json(json!({"error": "Email already in use"}))
+        }
+        Err(_) => {
+            HttpResponse::InternalServerError().json(json!({"error": "Failed to create user"}))
+        }
+    }
+}

+ 14 - 0
src/routes/users/get.rs

@@ -0,0 +1,14 @@
+use actix_web::{get, HttpResponse};
+use serde_json::json;
+
+use crate::auth::AuthUser;
+
+#[get("/user")]
+pub async fn get_user(auth: AuthUser) -> HttpResponse {
+    let user = auth.0;
+    HttpResponse::Ok().json(json!({
+        "id": user.id,
+        "name": user.name,
+        "email": user.email,
+    }))
+}

+ 88 - 0
src/routes/users/login.rs

@@ -0,0 +1,88 @@
+use actix_web::cookie::{Cookie, SameSite};
+use actix_web::{post, web, HttpResponse};
+use argon2::{
+    password_hash::{PasswordHash, PasswordVerifier},
+    Argon2,
+};
+use jsonwebtoken::{encode, EncodingKey, Header};
+use serde::Deserialize;
+use serde_json::json;
+use sqlx::PgPool;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use crate::auth::Claims;
+use crate::models::user::User;
+use crate::Config;
+
+#[derive(Deserialize)]
+pub struct LoginInput {
+    pub email: String,
+    pub password: String,
+}
+
+#[post("/user/login")]
+pub async fn login(
+    pool: web::Data<PgPool>,
+    config: web::Data<Config>,
+    body: web::Json<LoginInput>,
+) -> HttpResponse {
+    let email = body.email.to_lowercase();
+
+    let user = match User::find_by_email(pool.get_ref(), &email).await {
+        Ok(Some(u)) => u,
+        Ok(None) => {
+            return HttpResponse::Unauthorized()
+                .json(json!({"error": "Invalid email or password"}))
+        }
+        Err(_) => {
+            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
+        }
+    };
+
+    let parsed_hash = match PasswordHash::new(&user.pass_hash) {
+        Ok(h) => h,
+        Err(_) => {
+            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
+        }
+    };
+
+    if Argon2::default()
+        .verify_password(body.password.as_bytes(), &parsed_hash)
+        .is_err()
+    {
+        return HttpResponse::Unauthorized().json(json!({"error": "Invalid email or password"}));
+    }
+
+    let exp = (SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .unwrap()
+        .as_secs()
+        + 7 * 24 * 60 * 60) as usize;
+
+    let claims = Claims {
+        id: user.id.to_string(),
+        uuid_key: user.uuid_key.to_string(),
+        exp,
+    };
+
+    let token = match encode(
+        &Header::default(),
+        &claims,
+        &EncodingKey::from_secret(config.jwt_secret.as_bytes()),
+    ) {
+        Ok(t) => t,
+        Err(_) => {
+            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
+        }
+    };
+
+    let cookie = Cookie::build("token", token)
+        .http_only(true)
+        .secure(config.is_production)
+        .same_site(SameSite::Strict)
+        .path("/")
+        .max_age(actix_web::cookie::time::Duration::days(7))
+        .finish();
+
+    HttpResponse::Ok().cookie(cookie).json(json!({"success": true}))
+}

+ 18 - 0
src/routes/users/logout.rs

@@ -0,0 +1,18 @@
+use actix_web::cookie::{Cookie, SameSite};
+use actix_web::{post, web, HttpResponse};
+use serde_json::json;
+
+use crate::Config;
+
+#[post("/user/logout")]
+pub async fn logout(config: web::Data<Config>) -> HttpResponse {
+    let cookie = Cookie::build("token", "")
+        .http_only(true)
+        .secure(config.is_production)
+        .same_site(SameSite::Strict)
+        .path("/")
+        .max_age(actix_web::cookie::time::Duration::ZERO)
+        .finish();
+
+    HttpResponse::Ok().cookie(cookie).json(json!({"success": true}))
+}

+ 4 - 0
src/routes/users/mod.rs

@@ -0,0 +1,4 @@
+pub mod create;
+pub mod get;
+pub mod login;
+pub mod logout;