From 9e588394911b897689780efa1b14c241f530eb36 Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 22 Sep 2025 21:57:31 +0800 Subject: [PATCH] feat: add book api --- src/api/book.rs | 14 +-- src/api/mod.rs | 3 +- src/api/transaction.rs | 217 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 src/api/transaction.rs diff --git a/src/api/book.rs b/src/api/book.rs index fed1b62..fdca92e 100644 --- a/src/api/book.rs +++ b/src/api/book.rs @@ -12,7 +12,7 @@ use crate::model::db::book::Column as BookColumn; use crate::model::db::book::Model as BookModel; use crate::model::db::prelude::Book; use crate::model::http_body::book; -use crate::model::http_body::book::{BookInfo, BookItem}; +use crate::model::http_body::book::{BookInfo, BookResp}; use crate::model::http_body::common::SimpleResponse; use crate::AppState; use sea_orm::sqlx::types::chrono::Local; @@ -32,7 +32,7 @@ pub fn get_nest_handlers() -> Router { async fn get_all_books_handler( state: State, claims: Claims, -) -> Result>, (StatusCode, String)> { +) -> Result>, (StatusCode, String)> { let uid: i64 = claims.uid.clone(); let all_books = Book::find() .filter(BookColumn::Uid.eq(uid)) @@ -40,9 +40,9 @@ async fn get_all_books_handler( .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - let mut books: Vec = Vec::new(); + let mut books: Vec = Vec::new(); for b in all_books { - let book_resp = BookItem { + let book_resp = BookResp { id: b.id.into(), name: b.name, }; @@ -56,7 +56,7 @@ async fn get_book_by_id_handler( Path(id): Path, state: State, claims: Claims, -) -> Result, (StatusCode, String)> { +) -> Result, (StatusCode, String)> { let uid: i64 = claims.uid.clone(); let book_query = Book::find() .filter(BookColumn::Uid.eq(uid)) @@ -65,10 +65,10 @@ async fn get_book_by_id_handler( .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - let book_resp: BookItem; + let book_resp: BookResp; match book_query { Some(b) => { - book_resp = BookItem { + book_resp = BookResp { id: b.id.into(), name: b.name, }; diff --git a/src/api/mod.rs b/src/api/mod.rs index cb1aa61..b7fa515 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,4 @@ pub mod book; pub mod category; -pub mod tag; \ No newline at end of file +pub mod tag; +pub mod transaction; \ No newline at end of file diff --git a/src/api/transaction.rs b/src/api/transaction.rs new file mode 100644 index 0000000..b4eb44d --- /dev/null +++ b/src/api/transaction.rs @@ -0,0 +1,217 @@ +use crate::middleware::auth::Claims; +use crate::model::db::category::Column as CategoryColumn; +use crate::model::db::prelude::Category as CategoryPrelude; +use crate::model::db::prelude::Transaction; +use crate::model::db::transaction::{ + ActiveModel as TransactionActiveModel, Column as TransactionColumn, Model as TransactionModel, +}; +use crate::model::http_body; +use crate::model::http_body::book::BookInfo; +use std::collections::HashMap; + +use crate::model::db::prelude::Tag as TagPrelude; +use crate::model::db::tag::{ + ActiveModel as TagActiveModel, Column as TagColumn, Model as TagModel, +}; +use crate::model::http_body::category::CategoryResp; +use crate::model::http_body::common::SimpleResponse; +use crate::model::http_body::transaction::{TransactionReq, TransactionResp}; +use crate::AppState; +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use axum_macros::debug_handler; +use sea_orm::sqlx::types::chrono::Local; +use sea_orm::{ColumnTrait, DatabaseConnection}; +use sea_orm::QueryFilter; +use sea_orm::{entity::*, query::*}; +use serde_json::error::Category; +use std::ptr::null; + +pub fn get_nest_handlers() -> Router { + Router::new() + .route("/{id}/update", post(update_transaction_handler)) + .route("/{id}", get(get_transaction_by_id_handler)) + .route( + "/", + post(create_transaction_handler).get(get_all_transactions_handler), + ) +} + +async fn update_transaction_handler( + Path(id): Path, + state: State, + claims: Claims, + Json(payload): Json, +) -> Result, (StatusCode, String)> { + let uid: i64 = claims.uid; + let exist_transaction = Transaction::find() + .filter(TransactionColumn::Id.eq(id)) + .filter(TransactionColumn::Uid.eq(uid)) + .one(&state.conn) + .await; + let mut resp = SimpleResponse { + code: 0, + message: "".to_string(), + }; + let transaction: TransactionModel; + match exist_transaction { + Err(_) => { + resp.code = 1; + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "connection_error".to_string(), + )); + } + Ok(tra) => match tra { + Some(tr) => { + transaction = tr; + } + _ => return Err((StatusCode::NOT_FOUND, "Transaction not found".to_string())), + }, + } + let mut tr_active: TransactionActiveModel = transaction.into(); + match payload.description { + None => {} + Some(input_desc) => { + tr_active.description = Set(input_desc); + } + } + // TODO category + let new_category_id: i64 = match payload.category_id { + None => { + return Err(( + StatusCode::BAD_REQUEST, + "category_id is not valid".to_string(), + )) + } + Some(cid_string) => match cid_string.parse::() { + Ok(cid) => cid, + Err(_) => { + return Err(( + StatusCode::BAD_REQUEST, + "category_id is not valid".to_string(), + )) + } + }, + }; + let new_category_id_exist = CategoryPrelude::find() + .filter(CategoryColumn::Id.eq(new_category_id)) + .filter(CategoryColumn::Uid.eq(uid)) + .all(&state.conn) + .await; + match new_category_id_exist { + Ok(_) => {} + Err(_) => { + return Err((StatusCode::NOT_FOUND, "category_id not found".to_string())); + } + } + + // TODO tags + let tag_exist = check_tags_exist(&state.conn, payload.tags).await; + let all_tag_exist: bool; + match tag_exist { + Ok(tag_res) => { + all_tag_exist = tag_res.values().all(|&exists| exists); + } + Err(_) => { + return Err((StatusCode::NOT_FOUND, "tag not found".to_string())); + } + } + if !all_tag_exist { + return Err((StatusCode::NOT_FOUND, "tag not found".to_string())); + } + + // TODO amounts + + // Update + tr_active.updated_at = Set(Local::now().naive_utc()); + let update_res = tr_active.update(&state.conn).await; + match update_res { + Ok(_) => { + resp.code = 0; + } + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "transaction_update_failed".to_string(), + )); + } + } + + Ok(Json(resp)) +} + +async fn create_transaction_handler() {} + +async fn get_transaction_by_id_handler( + Path(id): Path, + state: State, + claims: Claims, +) -> Result, (StatusCode, String)> { + let uid: i64 = claims.uid.clone(); + let transaction_query = Transaction::find() + .filter(TransactionColumn::Uid.eq(id)) + .filter(TransactionColumn::Id.eq(id)) + .one(&state.conn) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + + let response: TransactionResp; + match transaction_query { + None => { + return Err((StatusCode::NOT_FOUND, "Transaction not found".to_string())); + } + Some(x) => { + response = TransactionResp { + id: x.id, + description: x.description, + category: CategoryResp { + id: 0, + name: "".to_string(), + parent_id: 0, + }, + tags: vec![], + } + } + }; + + Ok(Json(response)) +} + +async fn get_all_transactions_handler() {} + +// 批量检查 TagModel 是否存在 +async fn check_tags_exist( + connection: &DatabaseConnection, + ids: Vec, +) -> Result, String> { + // 将 Vec 转换为 Vec,并处理可能的转换错误 + let ids_i64: Vec = ids + .into_iter() + .filter_map(|id| id.parse::().ok()) + .collect(); + + if ids_i64.is_empty() { + return Ok(HashMap::new()); + } + + // 构建 IN 查询条件 + let condition = Condition::any().add(TagColumn::Id.is_in(ids_i64.clone())); + + // 执行批量查询,获取存在的 TagModel + let found_tags = TagPrelude::find() + .filter(condition) + .all(connection) + .await + .map_err(|e| format!("Database error: {}", e))?; + + // 创建 HashMap 记录每个 ID 是否存在 + let mut result = HashMap::new(); + for id in ids_i64 { + result.insert(id, found_tags.iter().any(|tag| tag.id == id)); + } + + Ok(result) +}