Skip to main content

backbone_maintenance/presentation/http/
maintenance_request_handler.rs

1//! MaintenanceRequest REST handlers
2//!
3//! Generated by metaphor-schema. Do not edit manually.
4//!
5//! Uses Axum and backbone-core's BackboneCrudHandler for all 12 CRUD endpoints.
6
7use 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
16// Backbone framework imports
17use backbone_core::http::BackboneCrudHandler;
18
19// Auth integration (optional)
20#[cfg(feature = "auth")]
21use backbone_auth::middleware::AuthContext;
22#[cfg(feature = "auth")]
23use backbone_auth::AuthMiddleware;
24
25// Domain imports
26use crate::domain::entity::*;
27use crate::application::service::{MaintenanceRequestService, ServiceError};
28
29// DTO imports
30use crate::presentation::dto::{CreateMaintenanceRequestDto, UpdateMaintenanceRequestDto, PatchMaintenanceRequestDto, MaintenanceRequestResponseDto};
31
32
33/// Application error type
34#[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    // Domain-specific errors from hook rules
45    #[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
102/// Domain-specific error codes for MaintenanceRequest
103pub 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
113// =============================================================================
114// Route Configuration
115// =============================================================================
116
117/// Create Axum router with all 16 Backbone endpoints for MaintenanceRequest.
118///
119/// # Routes
120///
121/// | Method | Path | Description |
122/// |--------|------|-------------|
123/// | GET | /maintenance_requests | List with pagination |
124/// | POST | /maintenance_requests | Create new |
125/// | GET | /maintenance_requests/:id | Get by ID |
126/// | PUT | /maintenance_requests/:id | Full update |
127/// | PATCH | /maintenance_requests/:id | Partial update |
128/// | DELETE | /maintenance_requests/:id | Soft delete |
129/// | POST | /maintenance_requests/bulk | Bulk create |
130/// | POST | /maintenance_requests/upsert | Upsert |
131/// | GET | /maintenance_requests/trash | List deleted |
132/// | POST | /maintenance_requests/:id/restore | Restore |
133/// | DELETE | /maintenance_requests/empty | Empty trash |
134/// | GET | /maintenance_requests/:id/deleted | Get deleted by ID |
135/// | DELETE | /maintenance_requests/trash/:id | Permanent delete from trash |
136/// | GET | /maintenance_requests/count | Count active entities |
137/// | GET | /maintenance_requests/trash/count | Count deleted entities |
138///
139/// # Example
140///
141/// ```text
142/// let service = Arc::new(MaintenanceRequestService::with_repository(repository));
143/// let router = create_maintenance_request_routes(service);
144/// ```
145pub 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
152/// Create Axum router with only the read (GET) endpoints for MaintenanceRequest.
153///
154/// Safe for public, unauthenticated exposure (e.g., reference data).
155/// Mutations must be served separately via `create_maintenance_request_write_routes`,
156/// typically wrapped in an auth middleware layer.
157pub 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
164/// Create Axum router with only the write (mutation) endpoints for MaintenanceRequest.
165///
166/// These routes must NOT be publicly exposed. Wrap them with an auth
167/// middleware before nesting into the application router.
168///
169/// # This is unguarded generic CRUD, not a validated write path
170///
171/// These are plain create/update/patch/delete mutations over the entity row —
172/// they bypass all business invariants. If the module exposes a validated write
173/// service (e.g. a command router over its domain engine), serve THAT instead
174/// for any mutation that must respect domain rules.
175pub 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/// Create authenticated routes with auth middleware.
183///
184/// Requires the `auth` feature flag. The `AuthMiddleware` implementation
185/// is responsible for extracting and validating tokens, then providing
186/// an `AuthContext` via request extensions.
187#[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}