//! Kategori ve ödeme türü uç noktaları (hanenin tüm üyeleri kullanabilir): //! //! | Yöntem | Yol | İş | //! |---|---|---| //! | GET | `/api/categories?include_inactive=true` | listele | //! | POST | `/api/categories` | ekle | //! | PATCH | `/api/categories/{id}` | ad / emoji / aktiflik değiştir | //! | PUT | `/api/categories/order` | sırala | //! //! `/api/payment-methods` aynı yapıdadır (yalnızca `group` alanı yoktur). use axum::{ Json, extract::{Path, Query, State}, http::StatusCode, }; use mongodb::bson::Uuid; use serde::{Deserialize, Serialize}; use ts_rs::TS; use validator::Validate; use crate::{ auth::AuthUser, error::{AppError, AppResult}, extract::ValidJson, models::group::Group, services::catalog::{self, Catalog, Changes, Item}, state::AppState, }; // ---------- İstek / yanıt ---------- #[derive(Deserialize, Default)] pub struct ListQuery { #[serde(default)] include_inactive: bool, } #[derive(Deserialize, Validate, TS)] #[ts(export, rename = "CatalogCreateRequest", optional_fields)] pub struct CreateRequest { group: Option, #[validate(length(max = 40, message = "Ad en fazla 40 karakter olabilir"))] name: String, #[validate(length(max = 16, message = "Emoji çok uzun"))] emoji: Option, } #[derive(Deserialize, Validate, TS)] #[ts(export, rename = "CatalogUpdateRequest", optional_fields)] pub struct UpdateRequest { #[validate(length(max = 40, message = "Ad en fazla 40 karakter olabilir"))] name: Option, #[validate(length(max = 16, message = "Emoji çok uzun"))] emoji: Option, is_active: Option, version: Option, } #[derive(Deserialize, Validate, TS)] #[ts(export, rename = "CatalogOrderRequest", optional_fields)] pub struct OrderRequest { group: Option, #[validate(length(max = 200, message = "Liste çok uzun"))] ids: Vec, } #[derive(Serialize, TS)] #[ts(export, rename = "CatalogItem")] pub struct ItemDto { id: String, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] group: Option, name: String, emoji: Option, sort_order: i32, is_active: bool, version: i32, } impl From for ItemDto { fn from(i: Item) -> Self { Self { id: i.id.to_string(), group: i.group, name: i.name, emoji: i.emoji, sort_order: i.sort_order, is_active: i.is_active, version: i.version, } } } fn dtos(items: Vec) -> Json> { Json(items.into_iter().map(ItemDto::from).collect()) } /// URL'deki kimlik bozuksa "bulunamadı" (başka hanenin kaydıyla aynı yanıt). fn parse_id(id: &str) -> AppResult { Uuid::parse_str(id).map_err(|_| AppError::NotFound) } // ---------- Ortak işleyiciler ---------- async fn list( c: Catalog, state: AppState, auth: AuthUser, q: ListQuery, ) -> AppResult>> { let items = catalog::list(&state.db, c, auth.household_id, q.include_inactive).await?; Ok(dtos(items)) } async fn create( c: Catalog, state: AppState, auth: AuthUser, req: CreateRequest, ) -> AppResult<(StatusCode, Json)> { let item = catalog::create( &state.db, c, auth.household_id, req.group, req.name, req.emoji, ) .await?; Ok((StatusCode::CREATED, Json(item.into()))) } async fn update( c: Catalog, state: AppState, auth: AuthUser, id: String, req: UpdateRequest, ) -> AppResult> { let changes = Changes { name: req.name, emoji: req.emoji, is_active: req.is_active, version: req.version, }; let item = catalog::update(&state.db, c, auth.household_id, parse_id(&id)?, changes).await?; Ok(Json(item.into())) } async fn reorder( c: Catalog, state: AppState, auth: AuthUser, req: OrderRequest, ) -> AppResult>> { let ids = req .ids .iter() .map(Uuid::parse_str) .collect::, _>>() .map_err(|_| AppError::BadRequest("Sıralama listesinde geçersiz kimlik var".into()))?; let items = catalog::reorder(&state.db, c, auth.household_id, req.group, ids).await?; Ok(dtos(items)) } // ---------- Kategoriler ---------- pub async fn list_categories( State(s): State, auth: AuthUser, Query(q): Query, ) -> AppResult>> { list(Catalog::Categories, s, auth, q).await } pub async fn create_category( State(s): State, auth: AuthUser, ValidJson(req): ValidJson, ) -> AppResult<(StatusCode, Json)> { create(Catalog::Categories, s, auth, req).await } pub async fn update_category( State(s): State, auth: AuthUser, Path(id): Path, ValidJson(req): ValidJson, ) -> AppResult> { update(Catalog::Categories, s, auth, id, req).await } pub async fn reorder_categories( State(s): State, auth: AuthUser, ValidJson(req): ValidJson, ) -> AppResult>> { reorder(Catalog::Categories, s, auth, req).await } // ---------- Ödeme türleri ---------- pub async fn list_payment_methods( State(s): State, auth: AuthUser, Query(q): Query, ) -> AppResult>> { list(Catalog::PaymentMethods, s, auth, q).await } pub async fn create_payment_method( State(s): State, auth: AuthUser, ValidJson(req): ValidJson, ) -> AppResult<(StatusCode, Json)> { create(Catalog::PaymentMethods, s, auth, req).await } pub async fn update_payment_method( State(s): State, auth: AuthUser, Path(id): Path, ValidJson(req): ValidJson, ) -> AppResult> { update(Catalog::PaymentMethods, s, auth, id, req).await } pub async fn reorder_payment_methods( State(s): State, auth: AuthUser, ValidJson(req): ValidJson, ) -> AppResult>> { reorder(Catalog::PaymentMethods, s, auth, req).await }