Compare commits

..

2 Commits

Author SHA1 Message Date
brian
f915e72cf5 temp 2025-09-30 10:45:16 +08:00
brian
33cb0aaa33 feat: add transaction router 2025-09-22 23:15:20 +08:00
8 changed files with 286 additions and 4 deletions

1
.gitignore vendored
View File

@@ -4,4 +4,5 @@
.DS_Store .DS_Store
.env .env
conf.toml conf.toml
config.toml
.fleet\ .fleet\

62
src/api/account.rs Normal file
View File

@@ -0,0 +1,62 @@
use crate::middleware::auth::Claims;
use crate::model::db::account::{
ActiveModel as AccountActiveModel, Column as AccountColumn, Model as AccountModel,
};
use crate::model::db::prelude::Account as AccountPrelude;
use crate::model::http_body::account::{AccountReq, AccountResp};
use crate::model::http_body::common::SimpleResponse;
use crate::AppState;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::routing::{get, post};
use axum::{Json, Router};
use sea_orm::sqlx::types::chrono::Local;
use sea_orm::{ActiveModelTrait, DbErr, Iden, Set};
pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new()
.route("/{id}/update", post(update_account_handler))
.route("/{id}", get(get_account_by_id_handler))
.route(
"/",
post(create_account_handler).get(get_all_accounts_handler),
)
}
async fn update_account_handler(
Path(id): Path<i64>,
state: State<AppState>,
claims: Claims,
Json(payload): Json<AccountReq>,
) -> Result<Json<SimpleResponse>, (StatusCode, String)> {
let uid: i64 = claims.uid.clone();
let mut active_model: AccountActiveModel = AccountPrelude::find_by_id(id)
.filter(AccountColumn::Uid.eq(uid))
.filter(AccountColumn::IsDeleted.eq(false))
.one(&state.conn)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
match payload.name {
Some(n) => {
active_model.name = Set(n);
}
_ => {}
}
active_model.updated_at = Set(Local::now().naive_utc());
active_model.update(&state.conn).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let resp = SimpleResponse{
code: 0,
message: "".to_string()
};
Ok(Json(resp))
}
async fn get_account_by_id_handler() {}
async fn create_account_handler() {}
async fn get_all_accounts_handler() {}

198
src/api/book_test.rs Normal file
View File

@@ -0,0 +1,198 @@
// #[cfg(test)]
// mod tests {
// use super::*;
// use axum::{
// http::{Request, StatusCode},
// Router,
// routing::{get, put},
// body::Body,
// };
// use sea_orm::{
// MockDatabase, MockExecResult, DatabaseConnection, DatabaseTransaction,
// entity::prelude::*,
// QueryFilter, Condition, DbErr, EntityTrait,
// };
// use serde_json::{json, Value};
// use tower::ServiceExt;
// use std::sync::Arc;
//
// // 模拟 Book 实体
// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
// #[sea_orm(table_name = "books")]
// pub struct Model {
// #[sea_orm(primary_key)]
// pub id: i32,
// pub title: String,
// pub author: String,
// }
//
// #[derive(Copy, Clone, Debug, EnumIter)]
// pub enum Relation {}
//
// impl Related<super::book::Entity> for Entity {
// fn to() -> RelationDef {
// panic!("No relations defined")
// }
// }
//
// // 创建测试用的 Router
// async fn setup_router(db: DatabaseConnection) -> Router {
// Router::new()
// .route("/books/:id", get(get_book_by_id).put(update_book_by_id))
// .route("/books", get(get_all_book))
// .with_state(Arc::new(db))
// }
//
// // 测试 get_book_by_id
// #[tokio::test]
// async fn test_get_book_by_id() {
// // 设置模拟数据库
// let db = MockDatabase::new(DatabaseBackend::Postgres)
// .append_query_results(vec![vec![Model {
// id: 1,
// title: "Test Book".to_string(),
// author: "Test Author".to_string(),
// }]])
// .into_connection();
//
// let app = setup_router(db).await;
//
// // 构造请求
// let request = Request::builder()
// .uri("/books/1")
// .method("GET")
// .body(Body::empty())
// .unwrap();
//
// // 发送请求
// let response = app.oneshot(request).await.unwrap();
// assert_eq!(response.status(), StatusCode::OK);
//
// // 解析响应
// let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
// let body: Value = serde_json::from_slice(&body).unwrap();
// assert_eq!(
// body,
// json!({
// "id": 1,
// "title": "Test Book",
// "author": "Test Author"
// })
// );
// }
//
// // 测试 get_book_by_id 未找到
// #[tokio::test]
// async fn test_get_book_by_id_not_found() {
// let db = MockDatabase::new(DatabaseBackend::Postgres)
// .append_query_results(vec![vec![] as Vec<Model>])
// .into_connection();
//
// let app = setup_router(db).await;
//
// let request = Request::builder()
// .uri("/books/999")
// .method("GET")
// .body(Body::empty())
// .unwrap();
//
// let response = app.oneshot(request).await.unwrap();
// assert_eq!(response.status(), StatusCode::NOT_FOUND);
// }
//
// // 测试 update_book_by_id
// #[tokio::test]
// async fn test_update_book_by_id() {
// let db = MockDatabase::new(DatabaseBackend::Postgres)
// .append_query_results(vec![vec![Model {
// id: 1,
// title: "Updated Book".to_string(),
// author: "Updated Author".to_string(),
// }]])
// .append_exec_results(vec![MockExecResult {
// last_insert_id: 1,
// rows_affected: 1,
// }])
// .into_connection();
//
// let app = setup_router(db).await;
//
// // 构造请求
// let request = Request::builder()
// .uri("/books/1")
// .method("PUT")
// .header("Content-Type", "application/json")
// .body(Body::from(
// json!({
// "title": "Updated Book",
// "author": "Updated Author"
// })
// .to_string(),
// ))
// .unwrap();
//
// // 发送请求
// let response = app.oneshot(request).await.unwrap();
// assert_eq!(response.status(), StatusCode::OK);
//
// // 解析响应
// let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
// let body: Value = serde_json::from_slice(&body).unwrap();
// assert_eq!(
// body,
// json!({
// "id": 1,
// "title": "Updated Book",
// "author": "Updated Author"
// })
// );
// }
//
// // 测试 get_all_book
// #[tokio::test]
// async fn test_get_all_book() {
// let db = MockDatabase::new(DatabaseBackend::Postgres)
// .append_query_results(vec![vec![
// Model {
// id: 1,
// title: "Book 1".to_string(),
// author: "Author 1".to_string(),
// },
// Model {
// id: 2,
// title: "Book 2".to_string(),
// author: "Author 2".to_string(),
// },
// ]])
// .into_connection();
//
// let app = setup_router(db).await;
//
// let request = Request::builder()
// .uri("/books")
// .method("GET")
// .body(Body::empty())
// .unwrap();
//
// let response = app.oneshot(request).await.unwrap();
// assert_eq!(response.status(), StatusCode::OK);
//
// let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
// let body: Value = serde_json::from_slice(&body).unwrap();
// assert_eq!(
// body,
// json!([
// {
// "id": 1,
// "title": "Book 1",
// "author": "Author 1"
// },
// {
// "id": 2,
// "title": "Book 2",
// "author": "Author 2"
// }
// ])
// );
// }
// }

View File

@@ -2,3 +2,4 @@ pub mod book;
pub mod category; pub mod category;
pub mod tag; pub mod tag;
pub mod transaction; pub mod transaction;
pub mod account;

View File

@@ -114,6 +114,7 @@ async fn start_server(config: &Config) {
.nest("/api/v1/book", api::book::get_nest_handlers()) .nest("/api/v1/book", api::book::get_nest_handlers())
.nest("/api/v1/category", api::category::get_nested_handlers()) .nest("/api/v1/category", api::category::get_nested_handlers())
.nest("/api/v1/tag", api::tag::get_nest_handlers()) .nest("/api/v1/tag", api::tag::get_nest_handlers())
.nest("/api/v1/transaction", api::transaction::get_nest_handlers())
.with_state(state) .with_state(state)
.layer(global_layer); .layer(global_layer);
let host = config.service.host.clone(); let host = config.service.host.clone();

View File

@@ -0,0 +1,14 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize)]
pub struct AccountResp {
pub id: String,
pub name: String,
pub account_type: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct AccountReq {
pub name: Option<String>,
pub account_type: Option<String>,
}

View File

@@ -3,3 +3,4 @@ pub mod common;
pub mod category; pub mod category;
pub mod tag; pub mod tag;
pub mod transaction; pub mod transaction;
pub mod account;

View File

@@ -19,3 +19,7 @@
pub tags: Vec<String>, pub tags: Vec<String>,
} }
pub struct TransactionAmountReq {
pub id: Option<String>,
pub account_id: Option<String>,
}