Rust actix webserver connects to MSSQL for tracking payment funding status.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
4.7 KiB

use chrono::NaiveDateTime;
use tiberius::{Client, Config, Query, AuthMethod, Row};
use tokio::net::TcpStream;
use tokio_util::compat::{TokioAsyncWriteCompatExt, Compat};
mod pymt_trans_table;
use pymt_trans_table::{PaymentData, FromSqlRow};
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, dev::Payload};
use serde::{Serialize, Deserialize};
use dotenv::dotenv;
use std::env::var;
type BDynError = Box< dyn std::error::Error>;
#[actix_web::main]
async fn main() -> Result<(), BDynError> {
HttpServer::new(|| {
App::new()
.service(search_contract)
})
.bind(("127.0.0.1", 8080))?
.run()
.await;
Ok(())
}
#[derive(Deserialize)]
struct ContractNumber {
contract_number: String
}
#[derive(Serialize)]
struct SearchResponse {
pub paid_in_full : Vec<PaymentData>,
pub checks: Option<Vec<[String;2]>>
}
#[get("/contract/{contract_number}")]
async fn search_contract(contract_number: web::Path<String>) -> HttpResponse {
let mut resp = SearchResponse{paid_in_full: vec![], checks: None};
dotenv().ok();
let mut config = Config::new();
config.host(var("HOST").expect("No HOST set in .env"));
config.port(var("PORT").expect("No HOST set in .env").parse::<u16>().expect("Could not parse port to u16"));
config.authentication(AuthMethod::windows(var("USER_NAME").expect("No USER_NAME set in .env"), var("PASSWORD").expect("No PASSWORD set in .env")));
config.trust_cert(); // on production, it is not a good idea to do this
let paid_in_full = get_paid_in_full(config.clone(), contract_number.to_string()).await;
let mut checks = vec![];
match paid_in_full {
Ok(pif) => {
if pif.len() > 0 {
for payment in pif {
if payment.is_check() {
checks.push(payment.check_info());
}
resp.paid_in_full.push(payment);
}
let found_checks = find_checks(config, checks).await;
match found_checks {
Ok(check_list) => {resp.checks = Some(check_list);},
Err(e) => {return HttpResponse::NotFound().body("Contract not found")}
}
};
},
Err(e) => {return HttpResponse::NotFound().body("Contract not found")}
}
HttpResponse::Ok().body(serde_json::json!(resp).to_string())
}
async fn create_sql_conn(config: Config) -> Result<Client<Compat<TcpStream>>, BDynError> {
let tcp = TcpStream::connect(config.get_addr()).await?;
tcp.set_nodelay(true)?;
Ok(Client::connect(config, tcp.compat_write()).await?)
}
fn create_table_struct<T>(sql_result: Vec<Row>) -> Result<Vec<T>, BDynError>
where T: FromSqlRow<T>
{
let mut table: Vec<T> = vec![];
for row in sql_result {
let raw_data = T::from_sql_row(row);
match raw_data {
Ok(structure_data) => table.push(structure_data),
Err(e) => {}
}
}
Ok(table)
}
async fn get_paid_in_full(config: Config, contract_number: String) -> Result<Vec<PaymentData>, BDynError> {
let mut client = create_sql_conn(config).await?;
let pif_query = Query::new(format!("USE NLCF SELECT TRXDSCRN as ContractNumber, BACHNUMB, VENDORID, PRCHAMNT, DOCDATE, DEX_ROW_TS FROM PM30200 WHERE TRXDSCRN = '{}'", contract_number));
let stream = pif_query.query(&mut client).await?;
let row = stream.into_first_result().await?;
create_table_struct(row)
}
async fn find_checks(config: Config, search_data: Vec<(String, String, NaiveDateTime)>) -> Result<Vec<[String;2]>, BDynError>{
use walkdir::{WalkDir, DirEntry};
let mut client = create_sql_conn(config).await?;
let mut found_checks: Vec<[String;2]> = vec![];
for (vendor_id, bach_num, timestamp) in search_data {
let check_query = Query::new(format!("USE NLCF SELECT VENDORID, CHEKNUMB, CHEKAMNT FROM ME240461
WHERE VENDORID = '{}' AND BACHNUMB= '{}' AND DOCDATE >= {}", vendor_id, bach_num, timestamp.format("%Y-%m-%d")));
let stream = check_query.query(&mut client).await?;
let rows = stream.into_first_result().await?;
if rows.len() < 1 {continue}
for row in rows {
let check_number: &str = row.get("CHEKNUMB").unwrap();
let vendor: &str = row.get("VENDORID").unwrap();
let files: Vec<DirEntry> = WalkDir::new(&format!("{}*{}*",
var("CHECK_FOLDER").expect("No HOST set in .env") ,check_number))
.into_iter().filter_map(|e| e.ok())
.collect();
if files.len() > 0 {found_checks.push([vendor.to_string(), files[0].file_name().to_str().unwrap().to_string()])}
}
}
Ok(found_checks)
}