WIP
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -553,6 +553,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ once_cell = "1.19.0"
|
|||||||
axum-macros = "0.4.1"
|
axum-macros = "0.4.1"
|
||||||
pbkdf2 = { version = "0.12", features = ["simple"] }
|
pbkdf2 = { version = "0.12", features = ["simple"] }
|
||||||
rand_core ={version = "0.6", features = ["std"]}
|
rand_core ={version = "0.6", features = ["std"]}
|
||||||
|
regex = {version = "1.10"}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
CREATE TABLE "categories" (
|
CREATE TABLE "categories" (
|
||||||
"id" BIGSERIAL PRIMARY KEY,
|
"id" BIGSERIAL PRIMARY KEY,
|
||||||
"uid" BIGINT NOT NULL,
|
"uid" BIGINT NOT NULL,
|
||||||
|
"book_id" BIGINT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"level" INT NOT NULL DEFAULT 0,
|
"level" INT NOT NULL DEFAULT 0,
|
||||||
"parent_category_id" BIGINT NOT NULL DEFAULT 0,
|
"parent_category_id" BIGINT NOT NULL DEFAULT 0,
|
||||||
@@ -14,6 +15,7 @@ CREATE TABLE "categories" (
|
|||||||
CREATE TABLE "tags" (
|
CREATE TABLE "tags" (
|
||||||
"id" BIGSERIAL PRIMARY KEY,
|
"id" BIGSERIAL PRIMARY KEY,
|
||||||
"uid" BIGINT NOT NULL,
|
"uid" BIGINT NOT NULL,
|
||||||
|
"book_id" BIGINT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"level" INT NOT NULL DEFAULT 0,
|
"level" INT NOT NULL DEFAULT 0,
|
||||||
"parent_tag_id" BIGINT NOT NULL DEFAULT 0,
|
"parent_tag_id" BIGINT NOT NULL DEFAULT 0,
|
||||||
@@ -66,9 +68,11 @@ CREATE TABLE "accounts" (
|
|||||||
CREATE TABLE "amounts" (
|
CREATE TABLE "amounts" (
|
||||||
"id" BIGSERIAL PRIMARY KEY,
|
"id" BIGSERIAL PRIMARY KEY,
|
||||||
"uid" BIGINT NOT NULL,
|
"uid" BIGINT NOT NULL,
|
||||||
|
"account_id" BIGINT NOT NULL,
|
||||||
"transaction_id" BIGINT NOT NULL,
|
"transaction_id" BIGINT NOT NULL,
|
||||||
"value" BIGINT NOT NULL DEFAULT 0,
|
"value" BIGINT NOT NULL DEFAULT 0,
|
||||||
"expo" BIGINT NOT NULL DEFAULT 5,
|
"expo" BIGINT NOT NULL DEFAULT 5,
|
||||||
|
"currency" TEXT NOT NULL DEFAULT '',
|
||||||
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
|
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||||
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
|
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ pub mod category;
|
|||||||
pub mod tag;
|
pub mod tag;
|
||||||
pub mod book;
|
pub mod book;
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
pub mod transaction;
|
||||||
|
|||||||
342
src/ledger/transaction.rs
Normal file
342
src/ledger/transaction.rs
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
use axum::extract::Query;
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use axum_macros::debug_handler;
|
||||||
|
use diesel::dsl::exists;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use std::fmt;
|
||||||
|
// use diesel::update;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
// use serde_json::to_string;
|
||||||
|
use crate::middleware::auth;
|
||||||
|
use crate::middleware::auth::Claims;
|
||||||
|
use crate::model::{db_model,schema,req};
|
||||||
|
use crate::util;
|
||||||
|
use crate::util::req::CommonResp;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
const PAYMENT_STORE_EXPO: i64 = 5;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SubmitTransactionRequest {
|
||||||
|
description: String,
|
||||||
|
book_id: i64,
|
||||||
|
category_id: i64,
|
||||||
|
tag_ids: Vec<i64>,
|
||||||
|
time: String,
|
||||||
|
amounts: Vec<SubmitTransactionAmountRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SubmitTransactionAmountRequest {
|
||||||
|
account_id: i64,
|
||||||
|
payment: String,
|
||||||
|
expo: i32,
|
||||||
|
currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CreateTransactionResponse {
|
||||||
|
pub id: i64,
|
||||||
|
pub book_id: i64,
|
||||||
|
pub description: String,
|
||||||
|
pub category_id: i64,
|
||||||
|
pub time: chrono::DateTime<Utc>,
|
||||||
|
pub tag_ids: Vec<i64>,
|
||||||
|
pub amount_ids: Vec<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_nest_handlers() -> Router<crate::AppState> {
|
||||||
|
Router::new()
|
||||||
|
.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("/entry/amount/:id", post(update_amount).get(get_amount)) // require query param tid=transaction_id
|
||||||
|
}
|
||||||
|
// implementation, or do something in between.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct TransactionError;
|
||||||
|
|
||||||
|
impl fmt::Display for TransactionError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "invalid transaction insert result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn create_transaction(
|
||||||
|
State(app_state): State<crate::AppState>,
|
||||||
|
claims: Claims,
|
||||||
|
Json(payload): Json<SubmitTransactionRequest>,
|
||||||
|
) -> Result<String, (StatusCode, String)> {
|
||||||
|
// ) -> Result<Json<db_model::Transaction>, (StatusCode, String)> {
|
||||||
|
let uid: i64 = claims.uid.clone();
|
||||||
|
let conn = app_state
|
||||||
|
.db
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
|
||||||
|
// 1. check related ids
|
||||||
|
// 1.1 check book id
|
||||||
|
if payload.book_id <= 0 {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "invalid book id".to_string()));
|
||||||
|
}
|
||||||
|
let check_book = conn
|
||||||
|
.interact(move |conn| {
|
||||||
|
diesel::select(exists(
|
||||||
|
schema::books::table
|
||||||
|
.filter(schema::books::uid.eq(uid))
|
||||||
|
.filter(schema::books::id.eq(payload.book_id)),
|
||||||
|
))
|
||||||
|
.get_result::<bool>(conn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
|
||||||
|
println!("book valid: {}", check_book);
|
||||||
|
if !check_book {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "invalid book id".to_string()));
|
||||||
|
}
|
||||||
|
// 1.2 check category id
|
||||||
|
if payload.category_id <= 0 {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "invalid category id".to_string()));
|
||||||
|
}
|
||||||
|
let check_category = conn
|
||||||
|
.interact(move |conn| {
|
||||||
|
diesel::select(exists(
|
||||||
|
schema::categories::table
|
||||||
|
.filter(schema::categories::uid.eq(uid))
|
||||||
|
.filter(schema::categories::id.eq(payload.category_id)),
|
||||||
|
))
|
||||||
|
.get_result::<bool>(conn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
|
||||||
|
println!("category valid: {}", check_category);
|
||||||
|
if !check_category {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "invalid category id".to_string()));
|
||||||
|
}
|
||||||
|
// 1.3 check tag ids
|
||||||
|
let payload_tag_size = payload.tag_ids.len() as i64;
|
||||||
|
let mut check_tag = payload_tag_size == 0;
|
||||||
|
if !check_tag {
|
||||||
|
let check_tag_count = conn
|
||||||
|
.interact(move |conn| {
|
||||||
|
schema::tags::table
|
||||||
|
.filter(schema::tags::uid.eq(uid))
|
||||||
|
.filter(schema::tags::id.eq_any(payload.tag_ids))
|
||||||
|
.select(diesel::dsl::count(schema::tags::id))
|
||||||
|
.first(conn)
|
||||||
|
.map(|x: i64| x as i64)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
println!("check tag: {}", check_tag_count);
|
||||||
|
check_tag = check_tag_count == payload_tag_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("tag valid: {}", check_tag);
|
||||||
|
|
||||||
|
if !check_tag {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "invalid tag ids".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.4 check account
|
||||||
|
let mut check_amount = true;
|
||||||
|
let mut amounts: Vec<db_model::AmountForm> = Vec::new();
|
||||||
|
for amount_req in payload.amounts {
|
||||||
|
// Parse and check payment
|
||||||
|
let parse_payment_result =
|
||||||
|
util::math::parse_payment_to_value_expo(amount_req.payment.clone(), PAYMENT_STORE_EXPO);
|
||||||
|
let value: i64;
|
||||||
|
let expo: i64;
|
||||||
|
match parse_payment_result {
|
||||||
|
Ok((val, expon)) => {
|
||||||
|
value = val;
|
||||||
|
expo = expon;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount = db_model::AmountForm {
|
||||||
|
uid: uid,
|
||||||
|
transaction_id: 0,
|
||||||
|
value: value,
|
||||||
|
expo: expo,
|
||||||
|
currency: amount_req.currency.clone(),
|
||||||
|
};
|
||||||
|
check_amount = check_amount && true;
|
||||||
|
amounts.push(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !check_amount || amounts.len() == 0 {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "invalid amount".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. build and insert into db
|
||||||
|
|
||||||
|
let mut transaction_resp: CreateTransactionResponse;
|
||||||
|
let mut amount_ids: Vec<i64> = Vec::new();
|
||||||
|
|
||||||
|
let transaction = conn
|
||||||
|
.interact(move |conn| {
|
||||||
|
conn.transaction(|conn| {
|
||||||
|
let new_transaction = db_model::TransactionForm {
|
||||||
|
id: None,
|
||||||
|
uid: uid,
|
||||||
|
book_id: payload.book_id,
|
||||||
|
description: payload.description,
|
||||||
|
category_id: payload.category_id,
|
||||||
|
// time: payload
|
||||||
|
time: Utc::now(),
|
||||||
|
};
|
||||||
|
let inserted_transactions = diesel::insert_into(schema::transactions::table)
|
||||||
|
.values(&new_transaction)
|
||||||
|
.returning(db_model::Transaction::as_returning())
|
||||||
|
.get_results(conn);
|
||||||
|
|
||||||
|
let mut new_tr_vec: Vec<db_model::Transaction>;
|
||||||
|
match inserted_transactions {
|
||||||
|
Ok(tr) => new_tr_vec = tr,
|
||||||
|
Err(e) => {
|
||||||
|
return diesel::result::QueryResult::Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut new_tid = 0 as i64;
|
||||||
|
let new_tr = new_tr_vec.get(0);
|
||||||
|
match new_tr {
|
||||||
|
Some(tr) =>new_tid = tr.id,
|
||||||
|
None => new_tid = 0,
|
||||||
|
}
|
||||||
|
if new_tid <= 0 {
|
||||||
|
return diesel::result::QueryResult::Err(diesel::result::Error::NotFound);
|
||||||
|
}
|
||||||
|
for amount in amounts.iter_mut() {
|
||||||
|
amount.transaction_id = new_tid;
|
||||||
|
}
|
||||||
|
let inserted_amounts = diesel::insert_into(schema::amounts::table)
|
||||||
|
.values(&amounts)
|
||||||
|
.returning(db_model::Amount::as_returning())
|
||||||
|
.get_results(conn);
|
||||||
|
let new_amounts: Vec<db_model::Amount> = match inserted_amounts {
|
||||||
|
Ok(ams) => ams,
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
};
|
||||||
|
for am in new_amounts {
|
||||||
|
amount_ids.push(am.id)
|
||||||
|
};
|
||||||
|
diesel::result::QueryResult::Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
|
||||||
|
// 3. build response data.
|
||||||
|
|
||||||
|
// Ok(Json(res))
|
||||||
|
Ok("finish".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_transaction(
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
State(app_state): State<crate::AppState>,
|
||||||
|
claims: Claims,
|
||||||
|
Json(payload): Json<SubmitTransactionRequest>,
|
||||||
|
) -> Result<Json<CommonResp>, (StatusCode, String)> {
|
||||||
|
let uid: i64 = claims.uid.clone();
|
||||||
|
let conn = app_state
|
||||||
|
.db
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
|
let res = conn
|
||||||
|
.interact(move |conn| {
|
||||||
|
diesel::update(schema::transactions::table)
|
||||||
|
.filter(schema::transactions::id.eq(id))
|
||||||
|
.filter(schema::transactions::uid.eq(uid))
|
||||||
|
.set((
|
||||||
|
schema::transactions::description.eq(payload.description),
|
||||||
|
schema::transactions::update_at.eq(now),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
let resp = util::req::CommonResp { code: 0 };
|
||||||
|
Ok(Json(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_transaction(
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
State(app_state): State<crate::AppState>,
|
||||||
|
claims: Claims,
|
||||||
|
) -> Result<Json<db_model::Transaction>, (StatusCode, String)> {
|
||||||
|
let uid: i64 = claims.uid.clone();
|
||||||
|
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::id.eq(id))
|
||||||
|
.filter(schema::transactions::uid.eq(uid))
|
||||||
|
.select(db_model::Transaction::as_select())
|
||||||
|
.limit(1)
|
||||||
|
.get_result(conn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(util::req::internal_error)?
|
||||||
|
.map_err(util::req::internal_error)?;
|
||||||
|
Ok(Json(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_transactions(
|
||||||
|
State(app_state): State<crate::AppState>,
|
||||||
|
claims: Claims,
|
||||||
|
) -> Result<Json<Vec<db_model::Transaction>>, (StatusCode, String)> {
|
||||||
|
let uid: i64 = claims.uid.clone();
|
||||||
|
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))
|
||||||
|
.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<crate::AppState>,
|
||||||
|
claims: Claims,
|
||||||
|
Query(params): Query<Params>,
|
||||||
|
) -> Result<Json<Vec<db_model::Amount>>, (StatusCode, String)> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -95,6 +95,7 @@ async fn main() {
|
|||||||
.nest("/api/v1/tag", ledger::tag::get_nest_handlers())
|
.nest("/api/v1/tag", ledger::tag::get_nest_handlers())
|
||||||
.nest("/api/v1/book", ledger::book::get_nest_handlers())
|
.nest("/api/v1/book", ledger::book::get_nest_handlers())
|
||||||
.nest("/api/v1/account", ledger::account::get_nest_handlers())
|
.nest("/api/v1/account", ledger::account::get_nest_handlers())
|
||||||
|
.nest("/api/v1/transaction", ledger::transaction::get_nest_handlers())
|
||||||
.nest("/api/v1/user", user::handler::get_nest_handlers())
|
.nest("/api/v1/user", user::handler::get_nest_handlers())
|
||||||
.with_state(shared_state)
|
.with_state(shared_state)
|
||||||
.layer(global_layer);
|
.layer(global_layer);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::model::schema;
|
use crate::model::schema;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
|
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
|
||||||
#[diesel(table_name = schema::categories)]
|
#[diesel(table_name = schema::categories)]
|
||||||
@@ -66,7 +67,6 @@ pub struct BookForm {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
|
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
|
||||||
#[diesel(table_name = schema::accounts)]
|
#[diesel(table_name = schema::accounts)]
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
@@ -88,6 +88,57 @@ pub struct AccountForm {
|
|||||||
pub account_type: i64,
|
pub account_type: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[diesel(table_name = schema::transactions)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct Transaction {
|
||||||
|
pub id: i64,
|
||||||
|
uid: i64,
|
||||||
|
pub book_id: i64,
|
||||||
|
pub description: String,
|
||||||
|
pub category_id: i64,
|
||||||
|
pub time: chrono::DateTime<Utc>,
|
||||||
|
is_delete: bool,
|
||||||
|
create_at: chrono::NaiveDateTime,
|
||||||
|
update_at: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Insertable)]
|
||||||
|
#[diesel(table_name = schema::transactions)]
|
||||||
|
pub struct TransactionForm {
|
||||||
|
pub id: Option<i64>,
|
||||||
|
pub uid: i64,
|
||||||
|
pub book_id: i64,
|
||||||
|
pub description: String,
|
||||||
|
pub category_id: i64,
|
||||||
|
pub time: chrono::DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[diesel(table_name = schema::amounts)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct Amount {
|
||||||
|
pub id: i64,
|
||||||
|
uid: i64,
|
||||||
|
transaction_id: i64,
|
||||||
|
value: i64,
|
||||||
|
expo: i64,
|
||||||
|
currency: String,
|
||||||
|
is_delete: bool,
|
||||||
|
create_at: chrono::NaiveDateTime,
|
||||||
|
update_at: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Insertable)]
|
||||||
|
#[diesel(table_name = schema::amounts)]
|
||||||
|
pub struct AmountForm {
|
||||||
|
pub uid: i64,
|
||||||
|
pub transaction_id: i64,
|
||||||
|
pub value: i64,
|
||||||
|
pub expo: i64,
|
||||||
|
pub currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Selectable, serde::Serialize)]
|
#[derive(Queryable, Selectable, serde::Serialize)]
|
||||||
#[diesel(table_name = schema::users)]
|
#[diesel(table_name = schema::users)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use serde::{de, Deserialize, Deserializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Params {
|
||||||
|
#[serde(default, deserialize_with="empty_string_as_none")]
|
||||||
|
transaction_id: Option<i64>,
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ diesel::table! {
|
|||||||
transaction_id -> Int8,
|
transaction_id -> Int8,
|
||||||
value -> Int8,
|
value -> Int8,
|
||||||
expo -> Int8,
|
expo -> Int8,
|
||||||
|
currency -> Text,
|
||||||
is_delete -> Bool,
|
is_delete -> Bool,
|
||||||
create_at -> Timestamp,
|
create_at -> Timestamp,
|
||||||
update_at -> Timestamp,
|
update_at -> Timestamp,
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ diesel::table! {
|
|||||||
amounts (id) {
|
amounts (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
uid -> Int8,
|
uid -> Int8,
|
||||||
|
account_id -> Int8,
|
||||||
transaction_id -> Int8,
|
transaction_id -> Int8,
|
||||||
value -> Int8,
|
value -> Int8,
|
||||||
expo -> Int8,
|
expo -> Int8,
|
||||||
|
currency -> Text,
|
||||||
is_delete -> Bool,
|
is_delete -> Bool,
|
||||||
create_at -> Timestamp,
|
create_at -> Timestamp,
|
||||||
update_at -> Timestamp,
|
update_at -> Timestamp,
|
||||||
@@ -40,6 +42,7 @@ diesel::table! {
|
|||||||
categories (id) {
|
categories (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
uid -> Int8,
|
uid -> Int8,
|
||||||
|
book_id -> Int8,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
level -> Int4,
|
level -> Int4,
|
||||||
parent_category_id -> Int8,
|
parent_category_id -> Int8,
|
||||||
@@ -53,6 +56,7 @@ diesel::table! {
|
|||||||
tags (id) {
|
tags (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
||||||
uid -> Int8,
|
uid -> Int8,
|
||||||
|
book_id -> Int8,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
level -> Int4,
|
level -> Int4,
|
||||||
parent_tag_id -> Int8,
|
parent_tag_id -> Int8,
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ pub async fn add_user(app_state: crate::AppState, username: String, password: St
|
|||||||
.get_result::<i64>(conn)
|
.get_result::<i64>(conn)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|res| {
|
.map_err(|_res| {
|
||||||
()
|
()
|
||||||
})?
|
})?
|
||||||
.map_err(|res| {
|
.map_err(|_res| {
|
||||||
()
|
()
|
||||||
})?;
|
})?;
|
||||||
println!("ret {}", res);
|
println!("ret {}", res);
|
||||||
@@ -50,10 +50,10 @@ pub async fn add_user(app_state: crate::AppState, username: String, password: St
|
|||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|_e| {
|
||||||
()
|
()
|
||||||
})?
|
})?
|
||||||
.map_err(|e| {
|
.map_err(|_e| {
|
||||||
()
|
()
|
||||||
})?;
|
})?;
|
||||||
let out = json!(add_res);
|
let out = json!(add_res);
|
||||||
@@ -72,7 +72,7 @@ pub async fn check_user_psw(app_state: crate::AppState, username: String, passwo
|
|||||||
});
|
});
|
||||||
let conn = match conn_res {
|
let conn = match conn_res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(err) => { return false; }
|
Err(_err) => { return false; }
|
||||||
};
|
};
|
||||||
// 1. get psw hash
|
// 1. get psw hash
|
||||||
let query_username = username.clone();
|
let query_username = username.clone();
|
||||||
|
|||||||
45
src/util/math.rs
Normal file
45
src/util/math.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
pub fn parse_payment_to_value_expo(payment_str: String, target_expo: i64) -> Result<(i64, i64), ()> {
|
||||||
|
// 1. check format
|
||||||
|
let re = Regex::new(r"[1-9]{0,9}[0-9]\.[0-9]{2,6}$").unwrap();
|
||||||
|
let res_format = re.is_match(payment_str.as_str());
|
||||||
|
if !res_format {
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
let mut value: i64 = 0;
|
||||||
|
let mut expo : i64 = 0;
|
||||||
|
|
||||||
|
let dot_index = payment_str.find('.');
|
||||||
|
let (int_part, decimal_part) = match dot_index {
|
||||||
|
Some(pos) => (&payment_str[..pos], &payment_str[pos+1..]),
|
||||||
|
None => (payment_str.as_str(), ""),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dec_part_padding = format!("{:0<width$}", decimal_part, width=target_expo as usize);
|
||||||
|
if dec_part_padding.len() > target_expo as usize {
|
||||||
|
let pd = &dec_part_padding[..target_expo as usize];
|
||||||
|
dec_part_padding = pd.to_string();
|
||||||
|
}
|
||||||
|
let num_str = format!("{}{}", int_part, dec_part_padding);
|
||||||
|
println!("parsed num string \"{}\"", num_str);
|
||||||
|
let num = num_str.parse::<i64>().unwrap();
|
||||||
|
let value = num;
|
||||||
|
let expo = target_expo;
|
||||||
|
|
||||||
|
Ok((value, expo))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_parse_payment(){
|
||||||
|
let r1 = parse_payment_to_value_expo("1.345".to_string(), 6);
|
||||||
|
assert_eq!(r1, Ok((1345000, 6)));
|
||||||
|
let r2 = parse_payment_to_value_expo("0.01".to_string(), 6);
|
||||||
|
assert_eq!(r2, Ok((10000, 6)));
|
||||||
|
let r3 = parse_payment_to_value_expo("0.10000001".to_string(), 6);
|
||||||
|
assert_eq!(r3, Err(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod req;
|
pub mod req;
|
||||||
pub mod pass;
|
pub mod pass;
|
||||||
|
pub mod math;
|
||||||
Reference in New Issue
Block a user