Skip to main content

backbone_inventory/presentation/http/
stock_item_handler.rs

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