exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }