Rearranges the Rust backend by domain: `modules/catalog` bundled four distinct concepts (Product, SKU, Category, Brand) behind one router and one 392-line service file, unlike every other module in the codebase which owns exactly one bounded aggregate. Splits into `modules/product` (Product/SKU, publish lifecycle, shop-scoped CRUD), `modules/category` (category tree, subtree query), and `modules/brand` (brand list, admin replace-all). `Category`/`Brand` move out of the shared `models.rs` into their owning modules; `Product`/`Sku` stay since `favorite`/`flash_sale`/`group_buying` reference them across modules. Purely internal restructuring — no route, schema, or behavior changes. Implements openspec change split-catalog-into-product-category-brand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
1.8 KiB
Rust
72 lines
1.8 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
use crate::error::{ApiError, ApiResult};
|
|
use crate::models::{Product, Sku};
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ProductWithSkus {
|
|
#[serde(flatten)]
|
|
pub product: Product,
|
|
pub skus: Vec<Sku>,
|
|
pub sold_count: i64,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct PublicListQuery {
|
|
pub page: Option<i64>,
|
|
pub per_page: Option<i64>,
|
|
pub category_id: Option<Uuid>,
|
|
pub brand_id: Option<Uuid>,
|
|
pub shop_id: Option<Uuid>,
|
|
pub q: Option<String>,
|
|
pub sort: Option<String>,
|
|
pub order: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum SortBy {
|
|
Newest,
|
|
Price,
|
|
Sales,
|
|
}
|
|
|
|
impl PublicListQuery {
|
|
pub fn sort_by(&self) -> ApiResult<SortBy> {
|
|
match self.sort.as_deref() {
|
|
None => Ok(SortBy::Newest),
|
|
Some("price") => Ok(SortBy::Price),
|
|
Some("sales") => Ok(SortBy::Sales),
|
|
Some(other) => Err(ApiError::BadRequest(format!("unsupported sort: {other}"))),
|
|
}
|
|
}
|
|
|
|
pub fn ascending(&self) -> ApiResult<bool> {
|
|
match self.order.as_deref() {
|
|
None | Some("asc") => Ok(true),
|
|
Some("desc") => Ok(false),
|
|
Some(other) => Err(ApiError::BadRequest(format!("unsupported order: {other}"))),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct ProductBody {
|
|
pub category_id: Option<Uuid>,
|
|
pub brand_id: Option<Uuid>,
|
|
pub slug: String,
|
|
pub name: serde_json::Value,
|
|
pub description: Option<serde_json::Value>,
|
|
pub images: Option<serde_json::Value>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct SkuBody {
|
|
pub sku_code: String,
|
|
pub attributes: Option<serde_json::Value>,
|
|
pub price_minor: i64,
|
|
pub currency: String,
|
|
pub stock: i32,
|
|
pub active: Option<bool>,
|
|
}
|