From 19ac57c338e8c4c56ce5c328ecf4055154ba2f73 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Fri, 4 Jul 2025 12:11:26 +0800 Subject: [PATCH] second commit --- .env | 2 ++ src/db.rs | 9 ++++----- src/errors.rs | 2 +- src/handlers.rs | 30 +++++++++++++++--------------- src/main.rs | 22 +++++++++++++++------- src/models.rs | 48 +++++------------------------------------------- 6 files changed, 42 insertions(+), 71 deletions(-) diff --git a/.env b/.env index 9547c50..5e88d0d 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ +INTRASYS_HOST=0.0.0.0 +INTRASYS_PORT=8080 PGUSER=postgres PGHOST=127.0.0.1 PGPASSWORD=password diff --git a/src/db.rs b/src/db.rs index 9426238..3bfc10a 100755 --- a/src/db.rs +++ b/src/db.rs @@ -1,11 +1,10 @@ +use sqlx::PgPool; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use sqlx::{PgPool, Postgres, Transaction}; use tracing::info; -pub type DbPool = PgPool; -pub type DbTransaction<'a> = Transaction<'a, Postgres>; +pub(crate) type DbPool = PgPool; -pub async fn create_pool() -> Result { +pub(crate) async fn create_pool() -> Result { info!("Creating database connection pool..."); PgPoolOptions::new() .max_connections(5) @@ -13,7 +12,7 @@ pub async fn create_pool() -> Result { .await } -pub async fn run_migrations(pool: &DbPool) -> Result<(), sqlx::migrate::MigrateError> { +pub(crate) async fn run_migrations(pool: &DbPool) -> Result<(), sqlx::migrate::MigrateError> { info!("Running database migrations..."); sqlx::migrate!("./migrations").run(pool).await } diff --git a/src/errors.rs b/src/errors.rs index 2d38625..f97a43f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,7 +6,7 @@ use sqlx::Error as SqlxError; use thiserror::Error; #[derive(Error, Debug)] -pub enum AppError { +pub(crate) enum AppError { #[error("Account not found: {0}")] AccountNotFound(i64), #[error("Account already exists")] diff --git a/src/handlers.rs b/src/handlers.rs index 126da51..49ebc10 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -6,7 +6,7 @@ use axum::{ }; use bigdecimal::BigDecimal; use sqlx::types::BigDecimal as SqlxBigDecimal; -use tracing::info; +use tracing::debug; use crate::{ db::DbPool, @@ -14,7 +14,7 @@ use crate::{ models::{Account, CreateAccount, TransactionRequest}, }; -pub async fn create_account( +pub(crate) async fn create_account( State(pool): State, Json(payload): Json, ) -> Result<(), AppError> { @@ -29,7 +29,7 @@ pub async fn create_account( "initial_balance is negative", ))); } - info!("Creating account: {:?}", payload.account_id); + debug!("Creating account: {:?}", payload.account_id); sqlx::query( r#" INSERT INTO accounts (account_id, balance) @@ -37,7 +37,7 @@ pub async fn create_account( "#, ) .bind(payload.account_id) - .bind(SqlxBigDecimal::from(payload.initial_balance)) + .bind(payload.initial_balance) .execute(&pool) .await .map_err(|err| match err { @@ -48,7 +48,7 @@ pub async fn create_account( Ok(()) } -pub async fn get_account( +pub(crate) async fn get_account( State(pool): State, Path(account_id): Path, ) -> Result, AppError> { @@ -57,7 +57,7 @@ pub async fn get_account( "account_id is invalid", ))); } - info!("Fetching account: {}", account_id); + debug!("Fetching account: {}", account_id); let account = sqlx::query_as::<_, Account>( r#" @@ -74,7 +74,7 @@ pub async fn get_account( Ok(Json(account)) } -pub async fn process_transaction( +pub(crate) async fn process_transaction( State(pool): State, Json(payload): Json, ) -> Result<(), AppError> { @@ -95,10 +95,6 @@ pub async fn process_transaction( "Minimum amount is 0.01", ))); } - info!( - "Processing transaction from {} to {} for amount {}", - payload.source_account_id, payload.destination_account_id, payload.amount - ); let mut tx = pool.begin().await.map_err(AppError::from)?; @@ -113,7 +109,7 @@ pub async fn process_transaction( .await .map_err(|_| AppError::AccountNotFound(payload.source_account_id))?; - if source_balance < SqlxBigDecimal::from(payload.amount.clone()) { + if source_balance < payload.amount.clone() { return Err(AppError::InsufficientFunds); } @@ -125,7 +121,7 @@ pub async fn process_transaction( WHERE account_id = $2 "#, ) - .bind(SqlxBigDecimal::from(payload.amount.clone())) + .bind(payload.amount.clone()) .bind(payload.source_account_id) .execute(&mut *tx) .await @@ -138,7 +134,7 @@ pub async fn process_transaction( WHERE account_id = $2 "#, ) - .bind(SqlxBigDecimal::from(payload.amount.clone())) + .bind(payload.amount.clone()) .bind(payload.destination_account_id) .execute(&mut *tx) .await @@ -156,11 +152,15 @@ pub async fn process_transaction( ) .bind(payload.source_account_id) .bind(payload.destination_account_id) - .bind(SqlxBigDecimal::from(payload.amount.clone())) + .bind(payload.amount.clone()) .execute(&mut *tx) .await .map_err(AppError::from)?; + debug!( + "Processing transaction from {} to {} for amount {}", + payload.source_account_id, payload.destination_account_id, payload.amount + ); tx.commit().await.map_err(AppError::from)?; Ok(()) diff --git a/src/main.rs b/src/main.rs index 509f04f..8c20bd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ +use std::env; + use axum::{ Router, routing::{get, post}, }; use dotenv::dotenv; -use tracing::Level; -use tracing_subscriber::{FmtSubscriber, layer::SubscriberExt, util::SubscriberInitExt}; +use tracing::{Level, info}; +use tracing_subscriber::FmtSubscriber; mod db; mod errors; @@ -13,12 +15,14 @@ mod models; #[tokio::main] async fn main() { + //Parse .env file and add the environmental variables configured there + dotenv().ok(); + let subscriber = FmtSubscriber::builder() .with_max_level(Level::TRACE) .finish(); tracing::subscriber::set_global_default(subscriber) .expect("Setting default tracing subscriber failed"); - dotenv().ok(); let pool = db::create_pool().await.expect("Failed to create pool"); @@ -27,15 +31,19 @@ async fn main() { .await .expect("Failed to run migrations"); - // Build our application's routes + // Build the application's routes let app = Router::new() .route("/accounts", post(handlers::create_account)) .route("/accounts/{account_id}", get(handlers::get_account)) .route("/transactions", post(handlers::process_transaction)) .with_state(pool); - // Run our app - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - tracing::debug!("listening on {}", listener.local_addr().unwrap()); + // Run the server + let host = env::var("INTRASYS_HOST").unwrap_or(String::from("127.0.0.1")); + let port = env::var("INTRASYS_PORT").unwrap_or(String::from("8080")); + let listener = tokio::net::TcpListener::bind(format!("{}:{}", host, port)) + .await + .unwrap(); + info!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); } diff --git a/src/models.rs b/src/models.rs index 4669eb0..186573a 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,61 +1,23 @@ -use std::str::FromStr; - -use bigdecimal::{BigDecimal, ParseBigDecimalError}; +use bigdecimal::BigDecimal; use serde::{Deserialize, Serialize}; -use sqlx::{Database, Decode, Encode, FromRow, Type, error::BoxDynError}; -use std::cmp::PartialOrd; -use std::error::Error; -use std::fmt::Display; -// use validator::{Validate, ValidateRange}; - -// #[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd, Clone, Type)] -// #[sqlx(transparent)] -// pub struct Amount(BigDecimal); - -// impl Amount { -// fn new(text: &str) -> Result { -// Ok(Amount(BigDecimal::from_str(text)?)) -// } -// } - -// impl Display for Amount { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// self.0.fmt(f) -// } -// } - -// impl ValidateRange for Amount { -// fn greater_than(&self, max: Amount) -> Option { -// Some(self > &max) -// } - -// fn less_than(&self, min: Amount) -> Option { -// Some(self < &min) -// } -// } - -// impl From for BigDecimal { -// fn from(value: Amount) -> Self { -// value.0 -// } -// } +use sqlx::FromRow; type Amount = BigDecimal; #[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct Account { +pub(crate) struct Account { pub account_id: i64, pub balance: Amount, } #[derive(Debug, Serialize, Deserialize)] -pub struct CreateAccount { +pub(crate) struct CreateAccount { pub account_id: i64, pub initial_balance: Amount, } #[derive(Debug, Serialize, Deserialize)] -pub struct TransactionRequest { +pub(crate) struct TransactionRequest { pub source_account_id: i64, pub destination_account_id: i64, pub amount: Amount,