try to do better

This commit is contained in:
arrelin
2026-02-12 18:43:36 +03:00
parent 3410786da7
commit 2f4e8af2a0
12 changed files with 439 additions and 52 deletions

View File

@@ -1,12 +1,11 @@
use axum::{
extract::{Path, State},
extract::{Path, Query, State},
http::StatusCode,
Json,
};
use sea_orm::{prelude::Decimal, DatabaseConnection};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use utoipa::{IntoParams, ToSchema};
use crate::models::expense::Model as ExpenseModel;
use crate::services::{CategoryService, ExpenseService};
@@ -31,6 +30,27 @@ pub struct RemainingLimitResponse {
pub remaining_limit: Decimal,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct MonthlyExpenseGroup {
pub year: i32,
pub month: u32,
pub total_amount: Decimal,
pub expenses: Vec<ExpenseModel>,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ExpenseHistoryResponse {
pub months: Vec<MonthlyExpenseGroup>,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct HistoryQueryParams {
#[serde(default)]
pub sort_order: Option<String>,
#[serde(default)]
pub show_archive: Option<bool>,
}
#[utoipa::path(
post,
path = "/families/{family_id}/categories/{category_id}/expenses",
@@ -183,6 +203,53 @@ pub async fn update_expense(
.map(Json)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
#[utoipa::path(
get,
path = "/families/{family_id}/categories/{category_id}/expenses/history",
tag = "expenses",
params(
("family_id" = i32, Path, description = "Family ID"),
("category_id" = i32, Path, description = "Category ID"),
HistoryQueryParams
),
responses(
(status = 200, description = "Expense history grouped by month", body = ExpenseHistoryResponse),
(status = 404, description = "Category not found"),
(status = 500, description = "Internal server error")
)
)]
pub async fn get_history(
State(db): State<DatabaseConnection>,
Path((family_id, category_id)): Path<(i32, i32)>,
Query(params): Query<HistoryQueryParams>,
) -> Result<Json<ExpenseHistoryResponse>, StatusCode> {
let category = CategoryService::find_by_id(&db, category_id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::NOT_FOUND)?;
if category.family_id != family_id {
return Err(StatusCode::NOT_FOUND);
}
let groups = ExpenseService::get_expense_history(
&db,
category_id,
params.sort_order,
params.show_archive.unwrap_or(false)
)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let months = groups.into_iter().map(|g| MonthlyExpenseGroup {
year: g.year,
month: g.month,
total_amount: g.total_amount,
expenses: g.expenses,
}).collect();
Ok(Json(ExpenseHistoryResponse { months }))
}
#[utoipa::path(
delete,
@@ -194,7 +261,7 @@ pub async fn update_expense(
("expense_id" = i32, Path, description = "Expense ID")
),
responses(
(status = 204, description = "Expense deleted successfully"),
(status = 200, description = "Expense deactivated successfully", body = ExpenseModel),
(status = 404, description = "Expense not found"),
(status = 500, description = "Internal server error")
)
@@ -202,7 +269,7 @@ pub async fn update_expense(
pub async fn delete_expense(
State(db): State<DatabaseConnection>,
Path((family_id, category_id, expense_id)): Path<(i32, i32, i32)>,
) -> Result<StatusCode, StatusCode> {
) -> Result<Json<ExpenseModel>, StatusCode> {
let category = CategoryService::find_by_id(&db, category_id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
@@ -223,7 +290,7 @@ pub async fn delete_expense(
ExpenseService::delete(&db, expense_id)
.await
.map(|_| StatusCode::NO_CONTENT)
.map(Json)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}