diff --git a/src/main.rs b/src/main.rs index 102babb..4683229 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,9 @@ extern crate rocket; mod user; use rocket::fs::FileServer; -use rocket::http::Status; use rocket::serde::{json::Json, Serialize}; -use rocket_db_pools::{sqlx, Connection, Database}; +use rocket_db_pools::{sqlx, Database}; use rocket_dyn_templates::{context, Template}; -use uuid::Uuid; -use bcrypt; -use user::{User, NewUser}; #[derive(Database)] #[database("rss_data")] @@ -36,114 +32,6 @@ fn index() -> Template { Template::render("index", context! {}) } -#[post("/users", data = "")] -async fn create_user( - mut db: Connection, - new_user: Json, -) -> Result, Status> { - let new_user = new_user.into_inner(); - - // Hash the password - we'll use bcrypt - let password_hash = bcrypt::hash(new_user.password.as_bytes(), bcrypt::DEFAULT_COST) - .map_err(|_| Status::InternalServerError)?; - - let user = User::new( - new_user.username, - password_hash, - new_user.email, - new_user.display_name - ); - - let query = sqlx::query( - "INSERT INTO users (id, username, password_hash, email, display_name, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6)" - ) - .bind(user.id.to_string()) - .bind(user.username.as_str()) - .bind(user.password_hash.as_str()) - .bind(user.email.as_ref()) - .bind(user.display_name.as_ref()) - .bind(user.created_at.to_rfc3339()) - .execute(&mut **db).await; - - match query { - Ok(_) => Ok(Json(user)), - Err(e) => { - eprintln!("Database error: {}", e); - match e { - sqlx::Error::Database(db_err) if db_err.is_unique_violation() => { - Err(Status::Conflict) - } - _ => Err(Status::InternalServerError), - } - } - } -} - -#[get("/users")] -async fn get_users(mut db: Connection) -> Result>, Status> { - let query = sqlx::query!( - r#" - SELECT - id as "id: String", - username, - password_hash, - email, - display_name, - created_at as "created_at: chrono::DateTime" - FROM users - "# - ) - .fetch_all(&mut **db) - .await - .map_err(|e| { - eprintln!("Database error: {}", e); - Status::InternalServerError - })?; - - // Convert the strings to UUIDs - let users = query - .into_iter() - .map(|row| User { - id: Uuid::parse_str(&row.id).unwrap(), - username: row.username, - password_hash: row.password_hash, - email: row.email, - display_name: row.display_name, - created_at: row.created_at, - }) - .collect::>(); - - Ok(Json(users)) -} - -#[delete("/users/")] -async fn delete_user(mut db: Connection, user_id: &str) -> Status { - // Validate UUID format - let uuid = match Uuid::parse_str(user_id) { - Ok(uuid) => uuid, - Err(_) => return Status::BadRequest, - }; - - let query = sqlx::query("DELETE FROM users WHERE id = ?") - .bind(uuid.to_string()) - .execute(&mut **db) - .await; - - match query { - Ok(result) => { - if result.rows_affected() > 0 { - Status::NoContent - } else { - Status::NotFound - } - } - Err(e) => { - eprintln!("Database error: {}", e); - Status::InternalServerError - } - } -} - #[get("/login")] fn login() -> Template { Template::render("login", context! {}) @@ -154,7 +42,14 @@ fn rocket() -> _ { rocket::build() .mount( "/", - routes![index, message, create_user, get_users, delete_user, login], + routes![ + index, + message, + login, + user::create_user, + user::get_users, + user::delete_user + ], ) .mount("/static", FileServer::from("static")) .attach(Template::fairing()) diff --git a/src/user.rs b/src/user.rs index acee07f..ce87fa9 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,6 +1,11 @@ use chrono; -use rocket::serde::{Deserialize, Serialize}; +use rocket::http::Status; +use rocket::serde::{json::Json, Deserialize, Serialize}; +use rocket_db_pools::Connection; use uuid::Uuid; +use bcrypt; + +use crate::Db; #[derive(Debug, Serialize)] #[serde(crate = "rocket::serde")] @@ -33,4 +38,112 @@ pub struct NewUser { pub password: String, pub email: Option, pub display_name: Option, +} + +#[post("/users", data = "")] +pub async fn create_user( + mut db: Connection, + new_user: Json, +) -> Result, Status> { + let new_user = new_user.into_inner(); + + // Hash the password - we'll use bcrypt + let password_hash = bcrypt::hash(new_user.password.as_bytes(), bcrypt::DEFAULT_COST) + .map_err(|_| Status::InternalServerError)?; + + let user = User::new( + new_user.username, + password_hash, + new_user.email, + new_user.display_name + ); + + let query = sqlx::query( + "INSERT INTO users (id, username, password_hash, email, display_name, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6)" + ) + .bind(user.id.to_string()) + .bind(user.username.as_str()) + .bind(user.password_hash.as_str()) + .bind(user.email.as_ref()) + .bind(user.display_name.as_ref()) + .bind(user.created_at.to_rfc3339()) + .execute(&mut **db).await; + + match query { + Ok(_) => Ok(Json(user)), + Err(e) => { + eprintln!("Database error: {}", e); + match e { + sqlx::Error::Database(db_err) if db_err.is_unique_violation() => { + Err(Status::Conflict) + } + _ => Err(Status::InternalServerError), + } + } + } +} + +#[get("/users")] +pub async fn get_users(mut db: Connection) -> Result>, Status> { + let query = sqlx::query!( + r#" + SELECT + id as "id: String", + username, + password_hash, + email, + display_name, + created_at as "created_at: chrono::DateTime" + FROM users + "# + ) + .fetch_all(&mut **db) + .await + .map_err(|e| { + eprintln!("Database error: {}", e); + Status::InternalServerError + })?; + + // Convert the strings to UUIDs + let users = query + .into_iter() + .map(|row| User { + id: Uuid::parse_str(&row.id).unwrap(), + username: row.username, + password_hash: row.password_hash, + email: row.email, + display_name: row.display_name, + created_at: row.created_at, + }) + .collect::>(); + + Ok(Json(users)) +} + +#[delete("/users/")] +pub async fn delete_user(mut db: Connection, user_id: &str) -> Status { + // Validate UUID format + let uuid = match Uuid::parse_str(user_id) { + Ok(uuid) => uuid, + Err(_) => return Status::BadRequest, + }; + + let query = sqlx::query("DELETE FROM users WHERE id = ?") + .bind(uuid.to_string()) + .execute(&mut **db) + .await; + + match query { + Ok(result) => { + if result.rows_affected() > 0 { + Status::NoContent + } else { + Status::NotFound + } + } + Err(e) => { + eprintln!("Database error: {}", e); + Status::InternalServerError + } + } } \ No newline at end of file