Skip to main content

backbone_inventory/presentation/http/
route_handler.rs

1//! Route 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;
13
14// Backbone framework imports
15use backbone_core::http::BackboneCrudHandler;
16
17// Auth integration (optional)
18#[cfg(feature = "auth")]
19use backbone_auth::middleware::AuthContext;
20#[cfg(feature = "auth")]
21use backbone_auth::AuthMiddleware;
22
23// Domain imports
24use crate::domain::entity::*;
25use crate::application::service::{RouteService, ServiceError};
26
27// DTO imports
28use crate::presentation::dto::{CreateRouteDto, UpdateRouteDto, PatchRouteDto, RouteResponseDto};
29
30
31/// Application error type
32#[derive(Debug, thiserror::Error)]
33pub enum RouteError {
34    #[error("Not found: {0}")]
35    NotFound(String),
36    #[error("Validation error: {0}")]
37    Validation(String),
38    #[error("Database error: {0}")]
39    Database(String),
40    #[error("Internal error: {0}")]
41    Internal(String),
42}
43
44impl From<ServiceError> for RouteError {
45    fn from(err: ServiceError) -> Self {
46        match err {
47            ServiceError::NotFound => Self::NotFound(err.to_string()),
48            ServiceError::Validation(ref msg) => Self::Validation(msg.clone()),
49            ServiceError::AlreadyExists(ref msg) => Self::Validation(msg.clone()),
50            ServiceError::Repository(ref e) => Self::Database(e.to_string()),
51            ServiceError::Internal(ref msg) => Self::Internal(msg.clone()),
52            ServiceError::Violations(_) => Self::Validation(err.to_string()),
53        }
54    }
55}
56
57impl axum::response::IntoResponse for RouteError {
58    fn into_response(self) -> axum::response::Response {
59        use axum::http::StatusCode;
60        use axum::Json;
61
62        let (status, code) = match &self {
63            Self::NotFound(_) => (StatusCode::NOT_FOUND, "ROUTE_NOT_FOUND"),
64            Self::Validation(_) => (StatusCode::BAD_REQUEST, "ROUTE_VALIDATION_ERROR"),
65            Self::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ROUTE_DATABASE_ERROR"),
66            Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ROUTE_INTERNAL_ERROR"),
67        };
68
69        let body = serde_json::json!({
70            "success": false,
71            "error": code,
72            "message": self.to_string(),
73        });
74
75        (status, Json(body)).into_response()
76    }
77}
78
79// =============================================================================
80// Route Configuration
81// =============================================================================
82
83/// The permissions this entity's generic routes require: `route:<action>` for `read`, `create`,
84/// `update`, `delete`, `restore` and `purge`, with any rename the schema's `requires:` declares.
85pub fn route_permissions() -> backbone_core::http::CrudPermissions {
86    backbone_core::http::CrudPermissions::new("route")
87        .in_module("inventory")
88}
89
90/// Create Axum router with all 16 Backbone endpoints for Route.
91///
92/// # Routes
93///
94/// | Method | Path | Description |
95/// |--------|------|-------------|
96/// | GET | /routes | List with pagination |
97/// | POST | /routes | Create new |
98/// | GET | /routes/:id | Get by ID |
99/// | PUT | /routes/:id | Full update |
100/// | PATCH | /routes/:id | Partial update |
101/// | DELETE | /routes/:id | Soft delete |
102/// | POST | /routes/bulk | Bulk create |
103/// | POST | /routes/upsert | Upsert |
104/// | GET | /routes/trash | List deleted |
105/// | POST | /routes/:id/restore | Restore |
106/// | DELETE | /routes/empty | Empty trash |
107/// | GET | /routes/:id/deleted | Get deleted by ID |
108/// | DELETE | /routes/trash/:id | Permanent delete from trash |
109/// | GET | /routes/count | Count active entities |
110/// | GET | /routes/trash/count | Count deleted entities |
111///
112/// # Example
113///
114/// ```text
115/// let service = Arc::new(RouteService::with_repository(repository));
116/// let router = create_route_routes(service);
117/// ```
118pub fn create_route_routes(service: Arc<RouteService>) -> Router {
119    BackboneCrudHandler::<RouteService, Route, CreateRouteDto, UpdateRouteDto, RouteResponseDto>::routes_guarded(
120        service,
121        "/routes",
122        route_permissions(),
123    )
124}
125
126/// Create Axum router with only the read (GET) endpoints for Route.
127///
128/// Each route requires the entity's `read` permission, so it needs an authenticated caller.
129/// Mutations must be served separately via `create_route_write_routes`,
130/// typically wrapped in an auth middleware layer.
131pub fn create_route_read_routes(service: Arc<RouteService>) -> Router {
132    BackboneCrudHandler::<RouteService, Route, CreateRouteDto, UpdateRouteDto, RouteResponseDto>::read_routes_guarded(
133        service,
134        "/routes",
135        route_permissions(),
136    )
137}
138
139/// Create Axum router with only the write (mutation) endpoints for Route.
140///
141/// These routes must NOT be publicly exposed. Wrap them with an auth
142/// middleware before nesting into the application router.
143///
144/// # This is unguarded generic CRUD, not a validated write path
145///
146/// These are plain create/update/patch/delete mutations over the entity row —
147/// they bypass all business invariants. If the module exposes a validated write
148/// service (e.g. a command router over its domain engine), serve THAT instead
149/// for any mutation that must respect domain rules.
150pub fn create_route_write_routes(service: Arc<RouteService>) -> Router {
151    BackboneCrudHandler::<RouteService, Route, CreateRouteDto, UpdateRouteDto, RouteResponseDto>::write_routes_guarded(
152        service,
153        "/routes",
154        route_permissions(),
155    )
156}
157
158/// Create authenticated routes with auth middleware.
159///
160/// Requires the `auth` feature flag. The `AuthMiddleware` implementation
161/// is responsible for extracting and validating tokens, then providing
162/// an `AuthContext` via request extensions.
163#[cfg(feature = "auth")]
164pub fn create_protected_route_routes<A: AuthMiddleware + Send + Sync + 'static>(
165    service: Arc<RouteService>,
166    auth: Arc<A>,
167) -> Router {
168    use axum::middleware;
169    use axum::response::IntoResponse;
170
171    let auth_layer = auth.clone();
172    create_route_routes(service)
173        .layer(middleware::from_fn(move |mut req: axum::extract::Request, next: axum::middleware::Next| {
174            let auth = auth_layer.clone();
175            async move {
176                let token = req.headers()
177                    .get(axum::http::header::AUTHORIZATION)
178                    .and_then(|h| h.to_str().ok())
179                    .and_then(|raw| raw.strip_prefix("Bearer ").or_else(|| raw.strip_prefix("bearer ")))
180                    .unwrap_or("");
181                match auth.authenticate(token).await {
182                    Ok(ctx) => {
183                        req.extensions_mut().insert(ctx);
184                        next.run(req).await
185                    }
186                    Err(_) => {
187                        (axum::http::StatusCode::UNAUTHORIZED,
188                         axum::Json(serde_json::json!({
189                             "success": false,
190                             "error": "unauthorized",
191                             "message": "Authentication required"
192                         }))
193                        ).into_response()
194                    }
195                }
196            }
197        }))
198}
199