//! Günlük defter uç noktaları (hanenin tüm üyeleri): //! //! | Yöntem | Yol | İş | //! |---|---|---| //! | GET | `/api/periods/{id}/entries?from=&to=` | dönemin kayıtları (tarih aralığı isteğe bağlı, ikisi de dahil) | //! | POST | `/api/periods/{id}/entries` | harcama ekle | //! | PATCH | `/api/entries/{id}` | düzenle | //! | DELETE | `/api/entries/{id}` | sil (soft) | //! | POST | `/api/entries/{id}/restore` | silmeyi geri al | 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::{entry::Entry, group::Group}, services::entry::{self, EntryChanges, EntryFilter, NewEntry}, state::AppState, }; #[derive(Deserialize, Validate, TS)] #[ts(export, rename = "EntryCreateRequest", optional_fields)] pub struct CreateRequest { /// `"YYYY-MM-DD"`, dönem içinde date: String, /// Kuruş, > 0 #[ts(type = "number")] amount: i64, /// Grup kategoriden alınır category_id: String, payment_method_id: Option, description: Option, } #[derive(Deserialize, Validate, TS)] #[ts(export, rename = "EntryUpdateRequest", optional_fields)] pub struct UpdateRequest { date: Option, #[ts(type = "number", optional)] amount: Option, category_id: Option, /// Boş metin → ödeme türünü kaldır payment_method_id: Option, /// Boş metin → açıklamayı kaldır description: Option, version: Option, } #[derive(Deserialize)] pub struct ListQuery { from: Option, to: Option, } #[derive(Serialize, TS)] #[ts(export, rename = "Entry")] pub struct EntryDto { id: String, period_id: String, date: String, /// Kuruş #[ts(type = "number")] amount: i64, group: Group, category_id: String, payment_method_id: Option, description: Option, created_by: String, version: i32, } impl From for EntryDto { fn from(e: Entry) -> Self { Self { id: e.id.to_string(), period_id: e.period_id.to_string(), date: e.date, amount: e.amount, group: e.group, category_id: e.category_id.to_string(), payment_method_id: e.payment_method_id.map(|u| u.to_string()), description: e.description, created_by: e.created_by.to_string(), version: e.version, } } } /// Adresteki kimlik → geçersizse 404 fn path_id(id: &str) -> AppResult { Uuid::parse_str(id).map_err(|_| AppError::NotFound) } /// Gövdedeki kimlik → geçersizse 400 (ör. kategori seçilmemiş) fn body_id(id: &str, what: &str) -> AppResult { Uuid::parse_str(id.trim()).map_err(|_| AppError::BadRequest(format!("{what} geçersiz"))) } pub async fn list( State(s): State, auth: AuthUser, Path(period_id): Path, Query(q): Query, ) -> AppResult>> { let items = entry::list( &s.db, auth.household_id, path_id(&period_id)?, EntryFilter { from: q.from, to: q.to, }, ) .await?; Ok(Json(items.into_iter().map(EntryDto::from).collect())) } pub async fn create( State(s): State, auth: AuthUser, Path(period_id): Path, ValidJson(req): ValidJson, ) -> AppResult<(StatusCode, Json)> { let payment_method_id = match req.payment_method_id.as_deref().map(str::trim) { None | Some("") => None, Some(id) => Some(body_id(id, "Ödeme türü")?), }; let e = entry::create( &s.db, auth.household_id, auth.user_id, path_id(&period_id)?, NewEntry { date: req.date, amount: req.amount, category_id: body_id(&req.category_id, "Kategori")?, payment_method_id, description: req.description, }, ) .await?; Ok((StatusCode::CREATED, Json(e.into()))) } pub async fn update( State(s): State, auth: AuthUser, Path(id): Path, ValidJson(req): ValidJson, ) -> AppResult> { let payment_method_id = match req.payment_method_id.as_deref().map(str::trim) { None => None, Some("") => Some(None), Some(pm) => Some(Some(body_id(pm, "Ödeme türü")?)), }; let category_id = req .category_id .as_deref() .map(|c| body_id(c, "Kategori")) .transpose()?; let e = entry::update( &s.db, auth.household_id, path_id(&id)?, EntryChanges { date: req.date, amount: req.amount, category_id, payment_method_id, description: req.description, version: req.version, }, ) .await?; Ok(Json(e.into())) } pub async fn delete( State(s): State, auth: AuthUser, Path(id): Path, ) -> AppResult { entry::delete(&s.db, auth.household_id, path_id(&id)?).await?; Ok(StatusCode::NO_CONTENT) } pub async fn restore( State(s): State, auth: AuthUser, Path(id): Path, ) -> AppResult> { let e = entry::restore(&s.db, auth.household_id, path_id(&id)?).await?; Ok(Json(e.into())) }