backbone_maintenance/presentation/http/
maintenance_request_handler.rs1use std::collections::HashMap;
8use std::sync::Arc;
9
10use axum::Router;
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13use chrono::{DateTime, Utc, NaiveDate};
14use rust_decimal::Decimal;
15
16use backbone_core::http::BackboneCrudHandler;
18
19#[cfg(feature = "auth")]
21use backbone_auth::middleware::AuthContext;
22#[cfg(feature = "auth")]
23use backbone_auth::AuthMiddleware;
24
25use crate::domain::entity::*;
27use crate::application::service::{MaintenanceRequestService, ServiceError};
28
29use crate::presentation::dto::{CreateMaintenanceRequestDto, UpdateMaintenanceRequestDto, PatchMaintenanceRequestDto, MaintenanceRequestResponseDto};
31
32
33#[derive(Debug, thiserror::Error)]
35pub enum MaintenanceRequestError {
36 #[error("Not found: {0}")]
37 NotFound(String),
38 #[error("Validation error: {0}")]
39 Validation(String),
40 #[error("Database error: {0}")]
41 Database(String),
42 #[error("Internal error: {0}")]
43 Internal(String),
44 #[error("A request cannot be scheduled to finish before it starts.: {0}")]
46 RequestScheduleEndBeforeStart(String),
47 #[error("A recurrence step must be at least one unit.: {0}")]
48 RequestRepeatIntervalBelowOne(String),
49 #[error("A recurrence bounded 'until' must state its repeat_until date.: {0}")]
50 RequestRepeatUntilMissing(String),
51 #[error("Only preventive maintenance may recur.: {0}")]
52 RequestCorrectiveCannotRecur(String),
53 #[error("Stage transitions are trigger-consistent; closing a recurring preventive request requires the managed transition verb.: {0}")]
54 MaintenanceRecurringCloseRequiresServiceVerb(String),
55 #[error("A stage referenced by live requests cannot be deleted.: {0}")]
56 MaintenanceStageInUse(String),
57 #[error("The stage foreign key is real, ON DELETE RESTRICT.: {0}")]
58 RequestStageFk(String),
59}
60
61impl From<ServiceError> for MaintenanceRequestError {
62 fn from(err: ServiceError) -> Self {
63 match err {
64 ServiceError::NotFound => Self::NotFound(err.to_string()),
65 ServiceError::Validation(ref msg) => Self::Validation(msg.clone()),
66 ServiceError::AlreadyExists(ref msg) => Self::Validation(msg.clone()),
67 ServiceError::Repository(ref e) => Self::Database(e.to_string()),
68 ServiceError::Internal(ref msg) => Self::Internal(msg.clone()),
69 }
70 }
71}
72
73impl axum::response::IntoResponse for MaintenanceRequestError {
74 fn into_response(self) -> axum::response::Response {
75 use axum::http::StatusCode;
76 use axum::Json;
77
78 let (status, code) = match &self {
79 Self::NotFound(_) => (StatusCode::NOT_FOUND, "MAINTENANCEREQUEST_NOT_FOUND"),
80 Self::Validation(_) => (StatusCode::BAD_REQUEST, "MAINTENANCEREQUEST_VALIDATION_ERROR"),
81 Self::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "MAINTENANCEREQUEST_DATABASE_ERROR"),
82 Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "MAINTENANCEREQUEST_INTERNAL_ERROR"),
83 Self::RequestScheduleEndBeforeStart(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_REQUEST_SCHEDULE_END_BEFORE_START"),
84 Self::RequestRepeatIntervalBelowOne(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_REQUEST_REPEAT_INTERVAL_BELOW_ONE"),
85 Self::RequestRepeatUntilMissing(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_REQUEST_REPEAT_UNTIL_MISSING"),
86 Self::RequestCorrectiveCannotRecur(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_REQUEST_CORRECTIVE_CANNOT_RECUR"),
87 Self::MaintenanceRecurringCloseRequiresServiceVerb(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_MAINTENANCE_RECURRING_CLOSE_REQUIRES_SERVICE_VERB"),
88 Self::MaintenanceStageInUse(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_MAINTENANCE_STAGE_IN_USE"),
89 Self::RequestStageFk(_) => (StatusCode::UNPROCESSABLE_ENTITY, "MAINTENANCEREQUEST_REQUEST_STAGE_FK"),
90 };
91
92 let body = serde_json::json!({
93 "success": false,
94 "error": code,
95 "message": self.to_string(),
96 });
97
98 (status, Json(body)).into_response()
99 }
100}
101
102pub mod maintenance_request_errors {
104 pub const REQUEST_SCHEDULE_END_BEFORE_START: &str = "MAINTENANCEREQUEST_REQUEST_SCHEDULE_END_BEFORE_START";
105 pub const REQUEST_REPEAT_INTERVAL_BELOW_ONE: &str = "MAINTENANCEREQUEST_REQUEST_REPEAT_INTERVAL_BELOW_ONE";
106 pub const REQUEST_REPEAT_UNTIL_MISSING: &str = "MAINTENANCEREQUEST_REQUEST_REPEAT_UNTIL_MISSING";
107 pub const REQUEST_CORRECTIVE_CANNOT_RECUR: &str = "MAINTENANCEREQUEST_REQUEST_CORRECTIVE_CANNOT_RECUR";
108 pub const MAINTENANCE_RECURRING_CLOSE_REQUIRES_SERVICE_VERB: &str = "MAINTENANCEREQUEST_MAINTENANCE_RECURRING_CLOSE_REQUIRES_SERVICE_VERB";
109 pub const MAINTENANCE_STAGE_IN_USE: &str = "MAINTENANCEREQUEST_MAINTENANCE_STAGE_IN_USE";
110 pub const REQUEST_STAGE_FK: &str = "MAINTENANCEREQUEST_REQUEST_STAGE_FK";
111}
112
113pub fn create_maintenance_request_routes(service: Arc<MaintenanceRequestService>) -> Router {
146 BackboneCrudHandler::<MaintenanceRequestService, MaintenanceRequest, CreateMaintenanceRequestDto, UpdateMaintenanceRequestDto, MaintenanceRequestResponseDto>::routes(
147 service,
148 "/maintenance_requests",
149 )
150}
151
152pub fn create_maintenance_request_read_routes(service: Arc<MaintenanceRequestService>) -> Router {
158 BackboneCrudHandler::<MaintenanceRequestService, MaintenanceRequest, CreateMaintenanceRequestDto, UpdateMaintenanceRequestDto, MaintenanceRequestResponseDto>::read_routes(
159 service,
160 "/maintenance_requests",
161 )
162}
163
164pub fn create_maintenance_request_write_routes(service: Arc<MaintenanceRequestService>) -> Router {
176 BackboneCrudHandler::<MaintenanceRequestService, MaintenanceRequest, CreateMaintenanceRequestDto, UpdateMaintenanceRequestDto, MaintenanceRequestResponseDto>::write_routes(
177 service,
178 "/maintenance_requests",
179 )
180}
181
182#[cfg(feature = "auth")]
188pub fn create_protected_maintenance_request_routes<A: AuthMiddleware + Send + Sync + 'static>(
189 service: Arc<MaintenanceRequestService>,
190 auth: Arc<A>,
191) -> Router {
192 use axum::middleware;
193 use axum::response::IntoResponse;
194
195 let auth_layer = auth.clone();
196 create_maintenance_request_routes(service)
197 .layer(middleware::from_fn(move |mut req: axum::extract::Request, next: axum::middleware::Next| {
198 let auth = auth_layer.clone();
199 async move {
200 let token = req.headers()
201 .get(axum::http::header::AUTHORIZATION)
202 .and_then(|h| h.to_str().ok())
203 .and_then(|raw| raw.strip_prefix("Bearer ").or_else(|| raw.strip_prefix("bearer ")))
204 .unwrap_or("");
205 match auth.authenticate(token).await {
206 Ok(ctx) => {
207 req.extensions_mut().insert(ctx);
208 next.run(req).await
209 }
210 Err(_) => {
211 (axum::http::StatusCode::UNAUTHORIZED,
212 axum::Json(serde_json::json!({
213 "success": false,
214 "error": "unauthorized",
215 "message": "Authentication required"
216 }))
217 ).into_response()
218 }
219 }
220 }
221 }))
222}