refactor(api): split catalog module into product, category, brand
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0b594c0147
commit
8446650baf
@@ -0,0 +1,71 @@
|
||||
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>,
|
||||
}
|
||||
Reference in New Issue
Block a user