feat: batch get for transaction and transaction amount

This commit is contained in:
acx
2024-09-17 15:02:46 +08:00
parent ceb3edba39
commit de38e20d3a
3 changed files with 138 additions and 2 deletions

View File

@@ -9,6 +9,7 @@ use axum_macros::debug_handler;
use diesel::dsl::exists; use diesel::dsl::exists;
use diesel::prelude::*; use diesel::prelude::*;
use std::fmt; use std::fmt;
use std::i64::MAX;
use chrono::ParseResult; use chrono::ParseResult;
// use diesel::update; // use diesel::update;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -20,7 +21,7 @@ use crate::util;
use crate::util::req::CommonResp; use crate::util::req::CommonResp;
use chrono::prelude::*; use chrono::prelude::*;
use tracing::info; use tracing::info;
use crate::model::req::GetAmountParams; use crate::model::req::{GetAmountByTransactionRangeParams, GetAmountParams, MAX_QUERY_LIMIT};
const PAYMENT_STORE_EXPO: i64 = 5; const PAYMENT_STORE_EXPO: i64 = 5;
@@ -53,15 +54,28 @@ pub struct CreateTransactionResponse {
pub amount_ids: Vec<i64>, pub amount_ids: Vec<i64>,
} }
#[derive(Deserialize)]
pub struct BatchGetTransactionRequest {
pub transaction_ids: Vec<i64>,
}
#[derive(Deserialize)]
pub struct BatchGetTransactionAmountRequest {
pub transaction_ids: Vec<i64>,
}
pub fn get_nest_handlers() -> Router<crate::AppState> { pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new() Router::new()
.route("/entry/batch_get", post(batch_get_transactions))
.route( .route(
"/entry", "/entry",
post(create_transaction) // create new transaction entry with amount post(create_transaction) // create new transaction entry with amount
.get(get_all_transactions),// get all transactions with entry .get(get_all_transactions),// get all transactions with entry
) )
.route("/entry/:id", get(get_transaction)) // get transaction 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 // .route("/entry/amount/:id", post(update_amount).get(get_amount)) // require query param tid=transaction_id
} }
// implementation, or do something in between. // implementation, or do something in between.
@@ -372,6 +386,28 @@ pub async fn get_all_transactions(
Ok(Json(res)) Ok(Json(res))
} }
pub async fn batch_get_transactions(
State(app_state): State<crate::AppState>,
claims: Claims,
Json(payload): Json<BatchGetTransactionRequest>,
) -> Result<Json<Vec<db_model::Transaction>>, (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( pub async fn get_amounts_by_tid(
State(app_state): State<crate::AppState>, State(app_state): State<crate::AppState>,
claims: Claims, 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)?
.map_err(util::req::internal_error)?; .map_err(util::req::internal_error)?;
Ok(Json(res)) Ok(Json(res))
}
pub async fn batch_get_amounts_by_tid(
State(app_state): State<crate::AppState>,
claims: Claims,
Json(payload): Json<BatchGetTransactionAmountRequest>,
) -> Result<Json<Vec<db_model::Amount>>, (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<crate::AppState>,
claims: Claims,
Query(params): Query<GetAmountByTransactionRangeParams>,
) -> Result<Json<Vec<db_model::Amount>>, (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))
} }

View File

@@ -6,6 +6,7 @@ use chrono::{DateTime, Utc};
#[diesel(table_name = schema::categories)] #[diesel(table_name = schema::categories)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Category { pub struct Category {
#[serde(with = "string")]
id: i64, id: i64,
uid: i64, uid: i64,
name: String, name: String,
@@ -58,6 +59,7 @@ pub struct TagForm {
#[diesel(table_name = schema::books)] #[diesel(table_name = schema::books)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Book { pub struct Book {
#[serde(with = "string")]
id: i64, id: i64,
uid: i64, uid: i64,
name: String, name: String,
@@ -100,10 +102,13 @@ pub struct AccountForm {
#[diesel(table_name = schema::transactions)] #[diesel(table_name = schema::transactions)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Transaction { pub struct Transaction {
#[serde(with = "string")]
pub id: i64, pub id: i64,
uid: i64, uid: i64,
#[serde(with = "string")]
pub book_id: i64, pub book_id: i64,
pub description: String, pub description: String,
#[serde(with = "string")]
pub category_id: i64, pub category_id: i64,
pub time: chrono::DateTime<Utc>, pub time: chrono::DateTime<Utc>,
#[serde(skip_serializing)] #[serde(skip_serializing)]
@@ -169,3 +174,25 @@ pub struct UserForm {
pub password: String, pub password: String,
pub mail: String, pub mail: String,
} }
mod string {
use std::fmt::Display;
use std::str::FromStr;
use serde::{de, Serializer, Deserialize, Deserializer};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where T: Display,
S: Serializer
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where T: FromStr,
T::Err: Display,
D: Deserializer<'de>
{
String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
}
}

View File

@@ -14,6 +14,13 @@ pub struct GetAmountParams {
pub transaction_id: Option<i64>, pub transaction_id: Option<i64>,
} }
#[derive(Debug, Deserialize)]
pub struct GetAmountByTransactionRangeParams {
pub transaction_id_from: Option<i64>,
pub transaction_id_to: Option<i64>,
pub limit: Option<i64>,
}
// Serde deserialization decorator to map empty Strings to None, // Serde deserialization decorator to map empty Strings to None,
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error> fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where where