diff --git a/backend/src/handlers/auth.rs b/backend/src/handlers/auth.rs index 947f5c9..b02b261 100644 --- a/backend/src/handlers/auth.rs +++ b/backend/src/handlers/auth.rs @@ -6,7 +6,7 @@ use argon2::{ }; use axum::{ Extension, Json, - extract::State, + extract::{Path, State}, http::{Response, StatusCode, header}, response::IntoResponse, }; @@ -18,7 +18,7 @@ use serde_json::json; use crate::{ AppState, cookie::jwt::encode_token, - models::{FilteredUser, LoginModel, LoginScheme, UserCreateScheme}, + models::{FilteredUser, LoginScheme, User, UserCreateScheme, UserUpdateScheme}, }; pub async fn create_user( @@ -88,7 +88,7 @@ pub async fn login( State(data): State>, Json(request): Json, ) -> Result)> { - let user = sqlx::query_as::<_, LoginModel>(r#"SELECT * FROM users WHERE username = $1"#) + let user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE username = $1"#) .bind(request.username) .fetch_optional(&data.db) .await @@ -131,7 +131,7 @@ pub async fn login( .http_only(true); let mut response = Response::new( - json!({"status": "success", "token": token, "user": filter_users(&user)}).to_string(), + json!({"status": "success", "token": token, "user": filter_user(&user)}).to_string(), ); response .headers_mut() @@ -156,24 +156,158 @@ pub async fn logout() -> Result, + Extension(state): Extension, ) -> Result)> { let response = json!({ "status": "success", "data": json!({ - "first_name": filter_users(&state).first_name, - "last_name": filter_users(&state).last_name + "first_name": filter_user(&state).first_name, + "last_name": filter_user(&state).last_name }) }); Ok(Json(response)) } -fn filter_users(user: &LoginModel) -> FilteredUser { +pub async fn delete_user( + Path(id): Path, + State(data): State>, +) -> Result)> { + let query = sqlx::query(r#"DELETE FROM users WHERE id = $1"#) + .bind(id) + .execute(&data.db) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"status": "error", "message": format!("{:?}", e)})), + ) + })?; + + if query.rows_affected() == 0 { + let error = json!({ + "status": "error", + "message": format!("User with ID {} not found", id) + }); + return Err((StatusCode::NOT_FOUND, Json(error))); + } + + Ok(StatusCode::NO_CONTENT) +} + +pub async fn get_users( + State(data): State>, +) -> Result)> { + let users = sqlx::query_as::<_, User>(r#"SELECT * FROM users ORDER BY last_name ASC"#) + .fetch_all(&data.db) + .await + .map_err(|e| { + let error = json!({ + "status": "error", + "message": format!("{:?}", e) + }); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error)) + })?; + + let response = users + .iter() + .map(|user| filter_user(&user)) + .collect::>(); + let json_respnse = json!(response); + Ok(Json(json_respnse)) +} + +pub async fn get_user_by_id( + Path(id): Path, + State(data): State>, +) -> Result)> { + let query = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#) + .bind(id) + .fetch_one(&data.db) + .await; + + match query { + Ok(user) => { + let response = serde_json::json!(filter_user(&user)); + return Ok(Json(response)); + } + Err(sqlx::Error::RowNotFound) => { + let error_response = serde_json::json!({ + "status": "fail", + "message": format!("User with ID {} not found", id) + }); + return Err((StatusCode::NOT_FOUND, Json(error_response))); + } + Err(e) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"status": "error", "message": format!("{:?}", e)})), + )); + } + }; +} + +pub async fn update_user( + Path(id): Path, + State(data): State>, + Json(body): Json, +) -> Result)> { + let argon = Argon2::default(); + let salt = SaltString::generate(&mut OsRng); + let hashed_pwd = match argon.hash_password(body.new_pwd.clone().as_bytes(), &salt) { + Ok(h) => h.to_string(), + Err(e) => panic!("Error hashing {:}", e), + }; + + let update_result = sqlx::query(r#"UPDATE users SET first_name = $1, last_name = $2, username = $3, pwd = $4, is_admin = $5 WHERE id = $6"#) + .bind(body.first_name.to_owned()) + .bind(body.last_name.to_owned()) + .bind(body.username.to_owned()) + .bind(hashed_pwd) + .bind(body.make_admin.to_owned()) + .bind(id) + .execute(&data.db) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"status": "error", "message": format!("{:?}", e)})), + ) + })?; + + if update_result.rows_affected() == 0 { + let error_response = serde_json::json!({ + "status": "error", + "message": format!("User with ID {} not found", id) + }); + return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))); + } + + let updated_user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#) + .bind(id) + .fetch_one(&data.db) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"status": "error", "message": format!("{:?}", e)})), + ) + })?; + + let response = serde_json::json!({ + "user": filter_user(&updated_user), + "status": "success" + }); + + Ok(Json(response)) +} + +fn filter_user(user: &User) -> FilteredUser { FilteredUser { id: user.id, first_name: user.first_name.clone(), last_name: user.last_name.clone(), + username: user.username.clone(), is_admin: user.is_admin.clone(), } } diff --git a/backend/src/models.rs b/backend/src/models.rs index 113f14b..244c488 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -27,11 +27,12 @@ pub struct TicketResponse { pub user_id: i16, } -#[derive(Deserialize, Serialize, PartialEq, Debug)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)] pub struct User { pub id: i16, pub last_name: String, pub first_name: String, + pub username: String, pub is_admin: bool, pub pwd: String, } @@ -49,6 +50,16 @@ pub struct TicketUpdateScheme { pub status: String, } +#[derive(Deserialize, Serialize, Debug)] +pub struct UserUpdateScheme { + pub id: i16, + pub first_name: String, + pub last_name: String, + pub username: String, + pub make_admin: bool, + pub new_pwd: String, +} + #[derive(Deserialize, Serialize, Debug, sqlx::FromRow)] pub struct UserCreateScheme { pub first_name: String, @@ -64,20 +75,12 @@ pub struct LoginScheme { pub pwd: String, } -#[derive(Deserialize, Serialize, Debug, Clone, sqlx::FromRow)] -pub struct LoginModel { - pub id: i16, - pub last_name: String, - pub first_name: String, - pub is_admin: bool, - pub pwd: String, -} - #[derive(Debug, Serialize)] pub struct FilteredUser { pub id: i16, pub first_name: String, pub last_name: String, + pub username: String, pub is_admin: bool, } diff --git a/backend/src/router.rs b/backend/src/router.rs index 788f298..ba2bf3a 100644 --- a/backend/src/router.rs +++ b/backend/src/router.rs @@ -8,7 +8,10 @@ use axum::{ use crate::{ AppState, handlers::{ - auth::{create_user, get_current_user, login, logout}, + auth::{ + create_user, delete_user, get_current_user, get_user_by_id, get_users, login, logout, + update_user, + }, ticket::{create_ticket, delete_ticket, edit_ticket, get_ticket_by_id, get_tickets}, }, }; @@ -26,6 +29,11 @@ pub fn create_router(state: Arc) -> Router { .route("/api/register", post(create_user)) .route("/api/login", post(login)) .route("/api/logout", get(logout)) - .route("/api/current_user", get(get_current_user)) + .route("/api/users", get(get_users)) + .route("/api/users/current", get(get_current_user)) + .route( + "/api/users/{id}", + get(get_user_by_id).delete(delete_user).patch(update_user), + ) .with_state(state) }