mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-22 06:28:19 +02:00
feat(rust) : serveur réécrit en Rust, Node retiré ; Calendrier en tête du menu (#571)
- Serveur Rust (server-rs/, axum + sqlx) : les 148 adresses de l'API, l'interface, les migrations de démarrage et les tâches de fond ; même comportement que Node (banc : 2027 cas, 0 écart hors écarts déclarés USERS-02, RAPPRO-21, R4, R5). - Node retiré : server/, dépendances serveur, image Docker sans Node (51 Mo), docker-compose à un seul service applicatif. - R4 : écritures d'un autre site refusées (Sec-Fetch-Site / Origin). - R5 : « aujourd'hui » et sauvegarde automatique à l'heure de Paris. - R6 : mainteneur dans .github/CODEOWNERS. - Menu latéral : Accueil puis Calendrier en tête. - Plan et état : docs/PLAN-MIGRATION-RUST.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM
This commit is contained in:
1 parent
6760f43e87
commit
600627c632
146 files changed
+31521
-20331
No files matched your search
@@ -0,0 +1,87 @@
|
||||
// Erreurs de l'API : { "message": … } avec le code HTTP, comme les gestionnaires Node.
|
||||
|
||||
use axum::Json;
|
||||
use axum::http::{HeaderValue, StatusCode, header};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppError {
|
||||
pub status: StatusCode,
|
||||
pub body: Value,
|
||||
}
|
||||
|
||||
pub type ApiResult<T = Response> = Result<T, AppError>;
|
||||
|
||||
impl AppError {
|
||||
pub fn new(status: StatusCode, message: &str) -> Self {
|
||||
Self {
|
||||
status,
|
||||
body: json!({ "message": message }),
|
||||
}
|
||||
}
|
||||
pub fn with_body(status: StatusCode, body: Value) -> Self {
|
||||
Self { status, body }
|
||||
}
|
||||
/// requireAuth (server/localAuth.ts)
|
||||
pub fn unauthorized() -> Self {
|
||||
Self::new(StatusCode::UNAUTHORIZED, "Authentification requise")
|
||||
}
|
||||
pub fn bad_request(message: &str) -> Self {
|
||||
Self::new(StatusCode::BAD_REQUEST, message)
|
||||
}
|
||||
pub fn forbidden(message: &str) -> Self {
|
||||
Self::new(StatusCode::FORBIDDEN, message)
|
||||
}
|
||||
pub fn not_found(message: &str) -> Self {
|
||||
Self::new(StatusCode::NOT_FOUND, message)
|
||||
}
|
||||
pub fn internal(message: &str) -> Self {
|
||||
Self::new(StatusCode::INTERNAL_SERVER_ERROR, message)
|
||||
}
|
||||
/// Erreur zod renvoyée telle quelle : { message, errors }
|
||||
pub fn validation(message: &str, issues: Vec<Value>) -> Self {
|
||||
Self::with_body(
|
||||
StatusCode::BAD_REQUEST,
|
||||
json!({ "message": message, "errors": issues }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(self.status, Json(self.body)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
/// Erreur imprévue (base, réseau) : journalisée, puis le message 500 du gestionnaire Node
|
||||
pub trait OrInternal<T> {
|
||||
fn or_500(self, message: &str) -> Result<T, AppError>;
|
||||
}
|
||||
|
||||
impl<T, E: std::fmt::Display> OrInternal<T> for Result<T, E> {
|
||||
fn or_500(self, message: &str) -> Result<T, AppError> {
|
||||
self.map_err(|error| {
|
||||
tracing::error!(%error, "{message}");
|
||||
AppError::internal(message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// res.json(undefined) : 200, type JSON, corps vide (ligne absente après une mise à jour)
|
||||
pub fn empty_json() -> Response {
|
||||
let mut response = StatusCode::OK.into_response();
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json; charset=utf-8"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
/// Réponse JSON d'une valeur sérialisable, ou corps vide si elle est absente
|
||||
pub fn json_or_empty<T: serde::Serialize>(value: Option<T>) -> Response {
|
||||
match value {
|
||||
Some(value) => Json(value).into_response(),
|
||||
None => empty_json(),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user