raisfast 0.2.23

The last backend you'll ever need. Rust-powered headless CMS with built-in blog, ecommerce, wallet, payment and 4 plugin engines.
use axum::Json;
use axum::extract::{Path, State};

use crate::dto::cart::{AddToCartRequest, CartResponse, UpdateCartItemRequest};
use crate::dto::order::{OrderItemResponse, OrderResponse};
use crate::errors::app_error::AppResult;
use crate::errors::response::ApiResponse;
use crate::errors::validation;
use crate::middleware::auth::AuthUser;

pub fn routes(
    registry: &mut crate::server::RouteRegistry,
    config: &crate::config::app::AppConfig,
) -> axum::Router<crate::AppState> {
    let restful = config.api_restful;
    let r = axum::Router::new();
    let r = reg_route!(
        r,
        registry,
        restful,
        "/cart",
        create,
        add_to_cart,
        "system authed",
        "cart"
    );
    let r = reg_route!(
        r,
        registry,
        restful,
        "/cart",
        get,
        list_cart,
        "system authed",
        "cart"
    );
    let r = reg_route!(
        r,
        registry,
        restful,
        "/cart/{id}",
        put,
        update_cart_item,
        "system authed",
        "cart"
    );
    let r = reg_route!(
        r,
        registry,
        restful,
        "/cart/{id}",
        delete,
        remove_from_cart,
        "system authed",
        "cart"
    );
    let r = reg_route!(
        r,
        registry,
        restful,
        "/cart",
        delete,
        clear_cart,
        "system authed",
        "cart"
    );
    reg_route!(
        r,
        registry,
        restful,
        "/cart/checkout",
        post,
        checkout,
        "system authed",
        "cart"
    )
}

fn to_order_response(
    o: crate::models::order::Order,
    items: Vec<crate::models::order_item::OrderItem>,
) -> OrderResponse {
    OrderResponse {
        id: o.id.to_string(),
        order_no: o.order_no,
        subtotal: o.subtotal,
        discount_amount: o.discount_amount,
        shipping_amount: o.shipping_amount,
        total_amount: o.total_amount,
        currency: o.currency,
        status: o.status.to_string(),
        buyer_name: o.buyer_name,
        buyer_phone: o.buyer_phone,
        buyer_email: o.buyer_email,
        shipping_address: o.shipping_address,
        tracking_no: o.tracking_no,
        carrier: o.carrier,
        remark: o.remark,
        admin_remark: o.admin_remark,
        delivery_data: o.delivery_data,
        paid_at: o.paid_at.map(|t| t.to_string()),
        completed_at: o.completed_at.map(|t| t.to_string()),
        cancelled_at: o.cancelled_at.map(|t| t.to_string()),
        created_at: o.created_at.to_string(),
        updated_at: o.updated_at.to_string(),
        items: items.into_iter().map(OrderItemResponse::from).collect(),
    }
}

#[utoipa::path(post, path = "/cart", tag = "cart",
    security(("bearer_auth" = [])),
    request_body = AddToCartRequest,
    responses((status = 200, description = "Item added to cart"))
)]
pub async fn add_to_cart(
    auth: AuthUser,
    State(state): State<crate::AppState>,
    Json(req): Json<AddToCartRequest>,
) -> AppResult<ApiResponse<()>> {
    let user_id = auth.ensure_snowflake_user_id()?;
    validation::validate(&req)?;
    state
        .cart_service
        .add_item(&auth, user_id, req.product_id, req.quantity, req.attributes)
        .await?;
    Ok(ApiResponse::success(()))
}

#[utoipa::path(get, path = "/cart", tag = "cart",
    security(("bearer_auth" = [])),
    responses((status = 200, description = "Cart items list"))
)]
pub async fn list_cart(
    auth: AuthUser,
    State(state): State<crate::AppState>,
) -> AppResult<ApiResponse<CartResponse>> {
    let user_id = auth.ensure_snowflake_user_id()?;
    let cart = state.cart_service.list_items(&auth, user_id).await?;
    Ok(ApiResponse::success(cart))
}

#[utoipa::path(put, path = "/cart/{id}", tag = "cart",
    security(("bearer_auth" = [])),
    params(("id" = String, Path, description = "Cart item ID")),
    request_body = UpdateCartItemRequest,
    responses((status = 200, description = "Cart item updated"))
)]
pub async fn update_cart_item(
    auth: AuthUser,
    State(state): State<crate::AppState>,
    Path(id): Path<String>,
    Json(req): Json<UpdateCartItemRequest>,
) -> AppResult<ApiResponse<()>> {
    let user_id = auth.ensure_snowflake_user_id()?;
    validation::validate(&req)?;
    let id = crate::types::snowflake_id::parse_id(&id)?;
    state
        .cart_service
        .update_quantity(&auth, id, user_id, req.quantity)
        .await?;
    Ok(ApiResponse::success(()))
}

#[utoipa::path(delete, path = "/cart/{id}", tag = "cart",
    security(("bearer_auth" = [])),
    params(("id" = String, Path, description = "Cart item ID")),
    responses((status = 200, description = "Cart item removed"))
)]
pub async fn remove_from_cart(
    auth: AuthUser,
    State(state): State<crate::AppState>,
    Path(id): Path<String>,
) -> AppResult<ApiResponse<()>> {
    let user_id = auth.ensure_snowflake_user_id()?;
    let id = crate::types::snowflake_id::parse_id(&id)?;
    state.cart_service.remove_item(&auth, id, user_id).await?;
    Ok(ApiResponse::success(()))
}

#[utoipa::path(delete, path = "/cart", tag = "cart",
    security(("bearer_auth" = [])),
    responses((status = 200, description = "Cart cleared"))
)]
pub async fn clear_cart(
    auth: AuthUser,
    State(state): State<crate::AppState>,
) -> AppResult<ApiResponse<()>> {
    let user_id = auth.ensure_snowflake_user_id()?;
    state.cart_service.clear_cart(&auth, user_id).await?;
    Ok(ApiResponse::success(()))
}

#[utoipa::path(post, path = "/cart/checkout", tag = "cart",
    security(("bearer_auth" = [])),
    responses((status = 200, description = "Order created from cart"))
)]
pub async fn checkout(
    auth: AuthUser,
    State(state): State<crate::AppState>,
) -> AppResult<ApiResponse<OrderResponse>> {
    let user_id = auth.ensure_snowflake_user_id()?;
    let (order, items) = state.cart_service.checkout(&auth, user_id).await?;
    Ok(ApiResponse::success(to_order_response(order, items)))
}