diff --git a/src/ledger/transaction.rs b/src/ledger/transaction.rs index 43b158a..1c4baf6 100644 --- a/src/ledger/transaction.rs +++ b/src/ledger/transaction.rs @@ -9,6 +9,7 @@ use axum_macros::debug_handler; use diesel::dsl::exists; use diesel::prelude::*; use std::fmt; +use std::i64::MAX; use chrono::ParseResult; // use diesel::update; use serde::{Deserialize, Serialize}; @@ -20,7 +21,7 @@ use crate::util; use crate::util::req::CommonResp; use chrono::prelude::*; use tracing::info; -use crate::model::req::GetAmountParams; +use crate::model::req::{GetAmountByTransactionRangeParams, GetAmountParams, MAX_QUERY_LIMIT}; const PAYMENT_STORE_EXPO: i64 = 5; @@ -53,15 +54,28 @@ pub struct CreateTransactionResponse { pub amount_ids: Vec, } +#[derive(Deserialize)] +pub struct BatchGetTransactionRequest { + pub transaction_ids: Vec, +} + +#[derive(Deserialize)] +pub struct BatchGetTransactionAmountRequest { + pub transaction_ids: Vec, +} + pub fn get_nest_handlers() -> Router { Router::new() + .route("/entry/batch_get", post(batch_get_transactions)) .route( "/entry", post(create_transaction) // create new transaction entry with amount .get(get_all_transactions),// get all transactions with entry ) .route("/entry/:id", get(get_transaction)) // get transaction entry - .route("/amount", get(get_amounts_by_tid)) + .route("/amount/by_transaction_id", get(get_amounts_by_tid)) + .route("/amount/batch_get_by_transaction_id", post(batch_get_amounts_by_tid)) + .route("/amount", get(get_all_amounts_by_tid_range)) // .route("/entry/amount/:id", post(update_amount).get(get_amount)) // require query param tid=transaction_id } // implementation, or do something in between. @@ -372,6 +386,28 @@ pub async fn get_all_transactions( Ok(Json(res)) } +pub async fn batch_get_transactions( + State(app_state): State, + claims: Claims, + Json(payload): Json, +) -> Result>, (StatusCode, String)> { + let uid = claims.uid.clone(); + if payload.transaction_ids.len() == 0 { + return Err((StatusCode::BAD_REQUEST, "no transaction_id list".to_string())); + } + let conn = app_state.db.get().await.map_err(util::req::internal_error)?; + let res = conn.interact(move |conn| { + schema::transactions::table + .filter(schema::transactions::uid.eq(uid)) + .filter(schema::transactions::is_delete.eq(false)) + .filter(schema::transactions::id.eq_any(payload.transaction_ids)) + .select(db_model::Transaction::as_select()) + .load(conn) + }) + .await.map_err(util::req::internal_error)?.map_err(util::req::internal_error)?; + Ok(Json(res)) +} + pub async fn get_amounts_by_tid( State(app_state): State, claims: Claims, @@ -398,4 +434,70 @@ pub async fn get_amounts_by_tid( .map_err(util::req::internal_error)? .map_err(util::req::internal_error)?; Ok(Json(res)) +} + +pub async fn batch_get_amounts_by_tid( + State(app_state): State, + claims: Claims, + Json(payload): Json, +) -> Result>, (StatusCode, String)> { + let uid = claims.uid.clone(); + if payload.transaction_ids.len() == 0 { + return Err((StatusCode::BAD_REQUEST, "no transaction_id list".to_string())); + } + let conn = app_state.db.get().await.map_err(util::req::internal_error)?; + let res = conn.interact(move |conn| { + schema::amounts::table + .filter(schema::amounts::uid.eq(uid)) + .filter(schema::amounts::is_delete.eq(false)) + .filter(schema::amounts::transaction_id.eq_any(payload.transaction_ids)) + .select(db_model::Amount::as_select()) + .load(conn) + }) + .await.map_err(util::req::internal_error)?.map_err(util::req::internal_error)?; + Ok(Json(res)) +} + +pub async fn get_all_amounts_by_tid_range( + State(app_state): State, + claims: Claims, + Query(params): Query, +) -> Result>, (StatusCode, String)> { + let uid: i64 = claims.uid.clone(); + let tid_from = match params.transaction_id_from { + None => {-1} + Some(id) => {id} + }; + let tid_to = match params.transaction_id_to { + None => {-1} + Some(id) => {id} + }; + if uid <= 0 || tid_from <= 0 || tid_to <= 0 || tid_from > tid_to { + return Err((StatusCode::BAD_REQUEST,"invalid values".to_string())); + } + let limit: i64 = match params.limit { + None => {MAX_QUERY_LIMIT as i64} + Some(i) => { + if i <= 0 { + MAX_QUERY_LIMIT as i64 + } else { + i as i64 + } + } + }; + let conn = app_state.db.get() + .await.map_err(util::req::internal_error)?; + let res = conn.interact(move |conn| { + schema::amounts::table + .filter(schema::amounts::uid.eq(uid)) + .filter(schema::amounts::is_delete.eq(false)) + .filter(schema::amounts::transaction_id.ge(tid_from)) + .filter(schema::amounts::transaction_id.le(tid_to)) + .limit(limit) + .select(db_model::Amount::as_select()) + .load(conn) + }).await + .map_err(util::req::internal_error)? + .map_err(util::req::internal_error)?; + Ok(Json(res)) } \ No newline at end of file diff --git a/src/model/db_model.rs b/src/model/db_model.rs index 6eb7354..c606a0e 100644 --- a/src/model/db_model.rs +++ b/src/model/db_model.rs @@ -6,6 +6,7 @@ use chrono::{DateTime, Utc}; #[diesel(table_name = schema::categories)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Category { + #[serde(with = "string")] id: i64, uid: i64, name: String, @@ -58,6 +59,7 @@ pub struct TagForm { #[diesel(table_name = schema::books)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Book { + #[serde(with = "string")] id: i64, uid: i64, name: String, @@ -100,10 +102,13 @@ pub struct AccountForm { #[diesel(table_name = schema::transactions)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Transaction { + #[serde(with = "string")] pub id: i64, uid: i64, + #[serde(with = "string")] pub book_id: i64, pub description: String, + #[serde(with = "string")] pub category_id: i64, pub time: chrono::DateTime, #[serde(skip_serializing)] @@ -169,3 +174,25 @@ pub struct UserForm { pub password: String, pub mail: String, } + +mod string { + use std::fmt::Display; + use std::str::FromStr; + + use serde::{de, Serializer, Deserialize, Deserializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where T: Display, + S: Serializer + { + serializer.collect_str(value) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where T: FromStr, + T::Err: Display, + D: Deserializer<'de> + { + String::deserialize(deserializer)?.parse().map_err(de::Error::custom) + } +} \ No newline at end of file diff --git a/src/model/req.rs b/src/model/req.rs index b20137f..7a5f072 100644 --- a/src/model/req.rs +++ b/src/model/req.rs @@ -14,6 +14,13 @@ pub struct GetAmountParams { pub transaction_id: Option, } +#[derive(Debug, Deserialize)] +pub struct GetAmountByTransactionRangeParams { + pub transaction_id_from: Option, + pub transaction_id_to: Option, + pub limit: Option, +} + // Serde deserialization decorator to map empty Strings to None, fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> where