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, pub checks: Option> } #[get("/contract/{contract_number}")] async fn search_contract(contract_number: web::Path) -> 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::().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>, 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(sql_result: Vec) -> Result, BDynError> where T: FromSqlRow { let mut table: Vec = 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, 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, 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 = 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) }