shipments.rs (6227B)
1 //! Shipment tracking API handlers 2 //! 3 //! This module contains the HTTP request handlers for shipment-related endpoints. 4 //! Database operations are delegated to the repository layer in `db::shipments`. 5 6 use axum::{ 7 extract::{Path, State}, 8 http::StatusCode, 9 response::{IntoResponse, Json}, 10 }; 11 use uuid::Uuid; 12 13 use crate::db; 14 use crate::models::{CreateShipmentRequest, UpdateShipmentStatusRequest}; 15 use crate::AppState; 16 17 /// Create a shipment for an order 18 /// 19 /// # Endpoint 20 /// `POST /orders/{order_uuid}/shipment` 21 pub async fn create_shipment( 22 State(state): State<AppState>, 23 Path(order_uuid): Path<Uuid>, 24 Json(request): Json<CreateShipmentRequest>, 25 ) -> impl IntoResponse { 26 // Look up the internal order ID from the public UUID 27 let order_id = match db::get_order_id_by_uuid(state.pool(), order_uuid).await { 28 Ok(Some(id)) => id, 29 Ok(None) => { 30 return ( 31 StatusCode::NOT_FOUND, 32 Json(serde_json::json!({ 33 "error": "Order not found", 34 "order_uuid": order_uuid.to_string() 35 })), 36 ) 37 .into_response(); 38 } 39 Err(e) => { 40 tracing::error!(error = %e, "Database error fetching order"); 41 return ( 42 StatusCode::INTERNAL_SERVER_ERROR, 43 Json(serde_json::json!({ 44 "error": "Failed to fetch order", 45 "details": e.to_string() 46 })), 47 ) 48 .into_response(); 49 } 50 }; 51 52 // Delegate shipment creation to the repository layer 53 match db::create_shipment( 54 state.pool(), 55 order_id, 56 &request.carrier, 57 request.tracking_number.as_deref(), 58 request.estimated_delivery_date, 59 ) 60 .await 61 { 62 Ok(shipment) => { 63 // Update order status to shipped 64 let _ = db::update_order_status(state.pool(), order_id, "shipped").await; 65 66 (StatusCode::CREATED, Json(shipment)).into_response() 67 } 68 Err(e) => { 69 tracing::error!(error = %e, "Database error creating shipment"); 70 ( 71 StatusCode::INTERNAL_SERVER_ERROR, 72 Json(serde_json::json!({ 73 "error": "Failed to create shipment", 74 "details": e.to_string() 75 })), 76 ) 77 .into_response() 78 } 79 } 80 } 81 82 /// Update shipment status 83 /// 84 /// # Endpoint 85 /// `PUT /orders/{order_uuid}/shipment/status` 86 pub async fn update_shipment_status( 87 State(state): State<AppState>, 88 Path(order_uuid): Path<Uuid>, 89 Json(request): Json<UpdateShipmentStatusRequest>, 90 ) -> impl IntoResponse { 91 // Start a transaction for the multi-step update 92 let mut tx = match state.pool().begin().await { 93 Ok(tx) => tx, 94 Err(e) => { 95 tracing::error!(error = %e, "Failed to start transaction"); 96 return ( 97 StatusCode::INTERNAL_SERVER_ERROR, 98 Json(serde_json::json!({ 99 "error": "Failed to start transaction", 100 "details": e.to_string() 101 })), 102 ) 103 .into_response(); 104 } 105 }; 106 107 // Look up the internal order ID within the transaction 108 let order_id = match db::get_order_id_by_uuid_in_tx(&mut tx, order_uuid).await { 109 Ok(Some(id)) => id, 110 Ok(None) => { 111 let _ = tx.rollback().await; 112 return ( 113 StatusCode::NOT_FOUND, 114 Json(serde_json::json!({ 115 "error": "Order not found", 116 "order_uuid": order_uuid.to_string() 117 })), 118 ) 119 .into_response(); 120 } 121 Err(e) => { 122 tracing::error!(error = %e, "Database error fetching order"); 123 let _ = tx.rollback().await; 124 return ( 125 StatusCode::INTERNAL_SERVER_ERROR, 126 Json(serde_json::json!({ 127 "error": "Failed to fetch order", 128 "details": e.to_string() 129 })), 130 ) 131 .into_response(); 132 } 133 }; 134 135 // Delegate shipment status update to the repository layer 136 match db::update_shipment_status( 137 &mut tx, 138 order_id, 139 &request.status, 140 request.actual_delivery_date, 141 ) 142 .await 143 { 144 Ok(true) => { 145 // If delivered, also update the order status 146 if request.status == "delivered" { 147 let _ = db::update_order_status_in_tx(&mut tx, order_id, "delivered").await; 148 } 149 150 if let Err(e) = tx.commit().await { 151 tracing::error!(error = %e, "Failed to commit transaction"); 152 return ( 153 StatusCode::INTERNAL_SERVER_ERROR, 154 Json(serde_json::json!({ 155 "error": "Failed to commit transaction", 156 "details": e.to_string() 157 })), 158 ) 159 .into_response(); 160 } 161 162 ( 163 StatusCode::OK, 164 Json(serde_json::json!({ 165 "success": true, 166 "message": "Shipment status updated", 167 "status": request.status 168 })), 169 ) 170 .into_response() 171 } 172 Ok(false) => { 173 let _ = tx.rollback().await; 174 ( 175 StatusCode::NOT_FOUND, 176 Json(serde_json::json!({ 177 "error": "Shipment not found for order", 178 "order_uuid": order_uuid.to_string() 179 })), 180 ) 181 .into_response() 182 } 183 Err(e) => { 184 tracing::error!(error = %e, "Database error updating shipment"); 185 let _ = tx.rollback().await; 186 ( 187 StatusCode::INTERNAL_SERVER_ERROR, 188 Json(serde_json::json!({ 189 "error": "Failed to update shipment", 190 "details": e.to_string() 191 })), 192 ) 193 .into_response() 194 } 195 } 196 }