This commit is contained in:
2026-05-16 17:57:54 +02:00
parent 1a2ec26f27
commit b2cefb7d66
55 changed files with 1654 additions and 334 deletions
+1 -1
View File
@@ -1 +1 @@
pub struct User {}
// Ce fichier est conservé pour la structure.
+25 -6
View File
@@ -1,10 +1,29 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateUserRequest {}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct CreateUserRequest {
pub username: String,
pub password: String,
pub pub_key: String,
pub is_superuser: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateUserRequest {}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct UpdateUserRequest {
pub username: String,
pub pub_key: String,
pub is_superuser: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserResponse {}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct UserResponse {
pub id: Uuid,
pub username: String,
pub pub_key: String,
pub is_superuser: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
+172 -12
View File
@@ -1,22 +1,182 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
use crate::core::state::AppState;
use crate::http::context::Superuser;
use crate::http::error::HTTPError;
use crate::routes::user::dto::{CreateUserRequest, UpdateUserRequest, UserResponse};
use crate::routes::user::mapper;
use axum::{
Json,
extract::{Path, State},
http::StatusCode,
};
use uuid::Uuid;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
/// Liste tous les utilisateurs
#[utoipa::path(
get,
path = "/users",
responses(
(status = 200, description = "Liste des utilisateurs récupérée avec succès", body = [UserResponse]),
(status = 401, description = "Non autorisé"),
(status = 403, description = "Interdit"),
(status = 500, description = "Erreur interne du serveur")
),
tag = "Users"
)]
pub async fn get_all(
_admin: Superuser,
State(state): State<AppState>,
) -> Result<Json<Vec<UserResponse>>, HTTPError> {
let users = state.repositories.user.get_all().await?;
Ok(Json(
users
.into_iter()
.map(mapper::user_model_to_user_response)
.collect(),
))
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
/// Récupère un utilisateur par son ID
#[utoipa::path(
get,
path = "/users/{id}",
responses(
(status = 200, description = "Utilisateur trouvé", body = UserResponse),
(status = 401, description = "Non autorisé"),
(status = 403, description = "Interdit"),
(status = 404, description = "Utilisateur non trouvé"),
(status = 500, description = "Erreur interne du serveur")
),
params(
("id" = Uuid, Path, description = "ID de l'utilisateur")
),
tag = "Users"
)]
pub async fn get_by_id(
_admin: Superuser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Json<UserResponse>, HTTPError> {
let user = state
.repositories
.user
.get_by_id(id)
.await?
.ok_or(HTTPError::NotFound)?;
Ok(Json(mapper::user_model_to_user_response(user)))
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
/// Crée un nouvel utilisateur
#[utoipa::path(
post,
path = "/users",
request_body = CreateUserRequest,
responses(
(status = 201, description = "Utilisateur créé avec succès", body = UserResponse),
(status = 400, description = "Requête invalide"),
(status = 401, description = "Non autorisé"),
(status = 403, description = "Interdit"),
(status = 500, description = "Erreur interne du serveur")
),
tag = "Users"
)]
pub async fn create(
_admin: Superuser,
State(state): State<AppState>,
Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), HTTPError> {
// Vérifier si le nom d'utilisateur existe déjà
if state
.repositories
.user
.username_exists(&payload.username)
.await?
{
return Err(HTTPError::BadRequest("Username already exists".to_string()));
}
let active_model = mapper::create_request_to_am(payload)
.map_err(|e| HTTPError::InternalServerError(e.to_string()))?;
let user = state.repositories.user.create(active_model).await?;
Ok((
StatusCode::CREATED,
Json(mapper::user_model_to_user_response(user)),
))
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
/// Met à jour un utilisateur existant
#[utoipa::path(
put,
path = "/users/{id}",
request_body = UpdateUserRequest,
responses(
(status = 200, description = "Utilisateur mis à jour avec succès", body = UserResponse),
(status = 401, description = "Non autorisé"),
(status = 403, description = "Interdit"),
(status = 404, description = "Utilisateur non trouvé"),
(status = 500, description = "Erreur interne du serveur")
),
params(
("id" = Uuid, Path, description = "ID de l'utilisateur")
),
tag = "Users"
)]
pub async fn update(
_admin: Superuser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
Json(payload): Json<UpdateUserRequest>,
) -> Result<Json<UserResponse>, HTTPError> {
// Vérifier l'existence
let user = state
.repositories
.user
.get_by_id(id)
.await?
.ok_or(HTTPError::NotFound)?;
// On pourrait aussi vérifier si le nouveau username existe déjà s'il a changé
if payload.username != user.username
&& state
.repositories
.user
.username_exists(&payload.username)
.await?
{
return Err(HTTPError::BadRequest("Username already exists".to_string()));
}
let active_model = mapper::update_request_to_am(user.id, payload);
let user = state.repositories.user.update(active_model).await?;
Ok(Json(mapper::user_model_to_user_response(user)))
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
/// Supprime un utilisateur
#[utoipa::path(
delete,
path = "/users/{id}",
responses(
(status = 204, description = "Utilisateur supprimé avec succès"),
(status = 401, description = "Non autorisé"),
(status = 403, description = "Interdit"),
(status = 404, description = "Utilisateur non trouvé"),
(status = 500, description = "Erreur interne du serveur")
),
params(
("id" = Uuid, Path, description = "ID de l'utilisateur")
),
tag = "Users"
)]
pub async fn delete(
_admin: Superuser,
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<StatusCode, HTTPError> {
if state.repositories.user.delete(id).await? {
Ok(StatusCode::NO_CONTENT)
} else {
Err(HTTPError::NotFound)
}
}
+39 -3
View File
@@ -1,5 +1,41 @@
use super::{domain::User, dto::UserResponse};
use crate::auth::password::hash_password;
use crate::models::user;
use crate::routes::user::dto::{CreateUserRequest, UpdateUserRequest, UserResponse};
use anyhow::Result as AnyResult;
use sea_orm::{NotSet, Set};
use uuid::Uuid;
pub fn to_response(_user: User) -> UserResponse {
todo!()
pub fn user_model_to_user_response(model: user::Model) -> UserResponse {
UserResponse {
id: model.id,
username: model.username,
pub_key: model.pub_key,
is_superuser: model.is_superuser,
created_at: model.created_at,
updated_at: model.updated_at,
}
}
pub fn create_request_to_am(payload: CreateUserRequest) -> AnyResult<user::ActiveModel> {
Ok(user::ActiveModel {
id: Set(Uuid::new_v4()),
username: Set(payload.username),
password: Set(hash_password(&payload.password)?),
pub_key: Set(payload.pub_key),
is_superuser: Set(payload.is_superuser),
created_at: Set(chrono::Utc::now()),
updated_at: Set(chrono::Utc::now()),
})
}
pub fn update_request_to_am(id: Uuid, payload: UpdateUserRequest) -> user::ActiveModel {
user::ActiveModel {
id: Set(id),
username: Set(payload.username),
password: NotSet, // On ne change pas le password via PUT général
pub_key: Set(payload.pub_key),
is_superuser: Set(payload.is_superuser),
updated_at: Set(chrono::Utc::now()),
..Default::default()
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
use crate::core::state::AppState;
use axum::{routing::get, Router};
use super::handlers;
pub fn router() -> Router {
pub fn router() -> Router<AppState> {
Router::new()
.route("/users", get(handlers::get_all).post(handlers::create))
.route(
+1 -21
View File
@@ -1,21 +1 @@
use super::domain::User;
pub async fn find_all() -> Vec<User> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<User> {
todo!()
}
pub async fn create(_user: User) -> User {
todo!()
}
pub async fn update(_id: u64, _user: User) -> Option<User> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
// Ce fichier est conservé pour la structure mais la logique est directement dans les handlers.