User listing, deleting and updating

This commit is contained in:
2026-04-25 14:33:02 +02:00
parent 6c1c13e088
commit f63131ebc7
3 changed files with 165 additions and 20 deletions

View File

@@ -6,7 +6,7 @@ use argon2::{
}; };
use axum::{ use axum::{
Extension, Json, Extension, Json,
extract::State, extract::{Path, State},
http::{Response, StatusCode, header}, http::{Response, StatusCode, header},
response::IntoResponse, response::IntoResponse,
}; };
@@ -18,7 +18,7 @@ use serde_json::json;
use crate::{ use crate::{
AppState, AppState,
cookie::jwt::encode_token, cookie::jwt::encode_token,
models::{FilteredUser, LoginModel, LoginScheme, UserCreateScheme}, models::{FilteredUser, LoginScheme, User, UserCreateScheme, UserUpdateScheme},
}; };
pub async fn create_user( pub async fn create_user(
@@ -88,7 +88,7 @@ pub async fn login(
State(data): State<Arc<AppState>>, State(data): State<Arc<AppState>>,
Json(request): Json<LoginScheme>, Json(request): Json<LoginScheme>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> { ) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
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) .bind(request.username)
.fetch_optional(&data.db) .fetch_optional(&data.db)
.await .await
@@ -131,7 +131,7 @@ pub async fn login(
.http_only(true); .http_only(true);
let mut response = Response::new( 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 response
.headers_mut() .headers_mut()
@@ -156,24 +156,158 @@ pub async fn logout() -> Result<impl IntoResponse, (StatusCode, Json<serde_json:
} }
pub async fn get_current_user( pub async fn get_current_user(
Extension(state): Extension<LoginModel>, Extension(state): Extension<User>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> { ) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
let response = json!({ let response = json!({
"status": "success", "status": "success",
"data": json!({ "data": json!({
"first_name": filter_users(&state).first_name, "first_name": filter_user(&state).first_name,
"last_name": filter_users(&state).last_name "last_name": filter_user(&state).last_name
}) })
}); });
Ok(Json(response)) Ok(Json(response))
} }
fn filter_users(user: &LoginModel) -> FilteredUser { pub async fn delete_user(
Path(id): Path<i32>,
State(data): State<Arc<AppState>>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
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<Arc<AppState>>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
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::<Vec<FilteredUser>>();
let json_respnse = json!(response);
Ok(Json(json_respnse))
}
pub async fn get_user_by_id(
Path(id): Path<i32>,
State(data): State<Arc<AppState>>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
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<i32>,
State(data): State<Arc<AppState>>,
Json(body): Json<UserUpdateScheme>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
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 { FilteredUser {
id: user.id, id: user.id,
first_name: user.first_name.clone(), first_name: user.first_name.clone(),
last_name: user.last_name.clone(), last_name: user.last_name.clone(),
username: user.username.clone(),
is_admin: user.is_admin.clone(), is_admin: user.is_admin.clone(),
} }
} }

View File

@@ -27,11 +27,12 @@ pub struct TicketResponse {
pub user_id: i16, pub user_id: i16,
} }
#[derive(Deserialize, Serialize, PartialEq, Debug)] #[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)]
pub struct User { pub struct User {
pub id: i16, pub id: i16,
pub last_name: String, pub last_name: String,
pub first_name: String, pub first_name: String,
pub username: String,
pub is_admin: bool, pub is_admin: bool,
pub pwd: String, pub pwd: String,
} }
@@ -49,6 +50,16 @@ pub struct TicketUpdateScheme {
pub status: String, 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)] #[derive(Deserialize, Serialize, Debug, sqlx::FromRow)]
pub struct UserCreateScheme { pub struct UserCreateScheme {
pub first_name: String, pub first_name: String,
@@ -64,20 +75,12 @@ pub struct LoginScheme {
pub pwd: String, 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)] #[derive(Debug, Serialize)]
pub struct FilteredUser { pub struct FilteredUser {
pub id: i16, pub id: i16,
pub first_name: String, pub first_name: String,
pub last_name: String, pub last_name: String,
pub username: String,
pub is_admin: bool, pub is_admin: bool,
} }

View File

@@ -8,7 +8,10 @@ use axum::{
use crate::{ use crate::{
AppState, AppState,
handlers::{ 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}, ticket::{create_ticket, delete_ticket, edit_ticket, get_ticket_by_id, get_tickets},
}, },
}; };
@@ -26,6 +29,11 @@ pub fn create_router(state: Arc<AppState>) -> Router {
.route("/api/register", post(create_user)) .route("/api/register", post(create_user))
.route("/api/login", post(login)) .route("/api/login", post(login))
.route("/api/logout", get(logout)) .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) .with_state(state)
} }