use chrono; 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")] pub struct User { pub id: Uuid, pub username: String, pub password_hash: String, pub email: Option, pub display_name: Option, pub created_at: chrono::DateTime, } impl User { pub fn new(username: String, password_hash: String, email: Option, display_name: Option) -> Self { User { id: Uuid::new_v4(), username, password_hash, email, display_name, created_at: chrono::Utc::now(), } } } #[derive(Debug, Deserialize)] #[serde(crate = "rocket::serde")] pub struct NewUser { pub username: String, 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 } } }