backbone_inventory/presentation/http/
stock_reconciliation_item_handler.rs1use std::collections::HashMap;
8use std::sync::Arc;
9
10use axum::Router;
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13use rust_decimal::Decimal;
14
15use backbone_core::http::BackboneCrudHandler;
17
18#[cfg(feature = "auth")]
20use backbone_auth::middleware::AuthContext;
21#[cfg(feature = "auth")]
22use backbone_auth::AuthMiddleware;
23
24use crate::domain::entity::*;
26use crate::application::service::{StockReconciliationItemService, ServiceError};
27
28use crate::presentation::dto::{CreateStockReconciliationItemDto, UpdateStockReconciliationItemDto, PatchStockReconciliationItemDto, StockReconciliationItemResponseDto};
30
31
32#[derive(Debug, thiserror::Error)]
34pub enum StockReconciliationItemError {
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 StockReconciliationItemError {
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 StockReconciliationItemError {
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, "STOCKRECONCILIATIONITEM_NOT_FOUND"),
65 Self::Validation(_) => (StatusCode::BAD_REQUEST, "STOCKRECONCILIATIONITEM_VALIDATION_ERROR"),
66 Self::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "STOCKRECONCILIATIONITEM_DATABASE_ERROR"),
67 Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "STOCKRECONCILIATIONITEM_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
80pub fn stock_reconciliation_item_permissions() -> backbone_core::http::CrudPermissions {
87 backbone_core::http::CrudPermissions::new("stock_reconciliation_item")
88 .in_module("inventory")
89}
90
91pub fn create_stock_reconciliation_item_routes(service: Arc<StockReconciliationItemService>) -> Router {
120 BackboneCrudHandler::<StockReconciliationItemService, StockReconciliationItem, CreateStockReconciliationItemDto, UpdateStockReconciliationItemDto, StockReconciliationItemResponseDto>::routes_guarded(
121 service,
122 "/stock_reconciliation_items",
123 stock_reconciliation_item_permissions(),
124 )
125}
126
127pub fn create_stock_reconciliation_item_read_routes(service: Arc<StockReconciliationItemService>) -> Router {
133 BackboneCrudHandler::<StockReconciliationItemService, StockReconciliationItem, CreateStockReconciliationItemDto, UpdateStockReconciliationItemDto, StockReconciliationItemResponseDto>::read_routes_guarded(
134 service,
135 "/stock_reconciliation_items",
136 stock_reconciliation_item_permissions(),
137 )
138}
139
140pub fn create_stock_reconciliation_item_write_routes(service: Arc<StockReconciliationItemService>) -> Router {
152 BackboneCrudHandler::<StockReconciliationItemService, StockReconciliationItem, CreateStockReconciliationItemDto, UpdateStockReconciliationItemDto, StockReconciliationItemResponseDto>::write_routes_guarded(
153 service,
154 "/stock_reconciliation_items",
155 stock_reconciliation_item_permissions(),
156 )
157}
158
159#[cfg(feature = "auth")]
165pub fn create_protected_stock_reconciliation_item_routes<A: AuthMiddleware + Send + Sync + 'static>(
166 service: Arc<StockReconciliationItemService>,
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_reconciliation_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