From aa4559be2a7d34a71d0c3f1836ce2e9d4acb0297 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 4 Aug 2023 20:09:09 -0400 Subject: [PATCH] refactored project for readability --- .env | 3 +- src/{card.rs => cards.rs} | 45 ++++++++++++++++++++++-- src/handlers.rs | 13 +++++++ src/main.rs | 74 ++++++++++++--------------------------- src/server.rs | 59 ++++++++++--------------------- src/templates.rs | 5 +++ src/util.rs | 45 ++++++++++++++++++++++++ 7 files changed, 149 insertions(+), 95 deletions(-) rename src/{card.rs => cards.rs} (55%) create mode 100644 src/handlers.rs create mode 100644 src/templates.rs create mode 100644 src/util.rs diff --git a/.env b/.env index c0d95b5..1b128c7 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -UPDATE_INTERVAL=12 \ No newline at end of file +UPDATE_INTERVAL=12 +PORT=8080 \ No newline at end of file diff --git a/src/card.rs b/src/cards.rs similarity index 55% rename from src/card.rs rename to src/cards.rs index 723f06a..3797b56 100644 --- a/src/card.rs +++ b/src/cards.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use reqwest; use prometheus::{GaugeVec, Opts}; +use std::{env, fs::File, fs::OpenOptions, io::BufReader, collections::HashMap}; +use crate::util; lazy_static::lazy_static! { pub static ref CARD_VALUES: GaugeVec = GaugeVec::new( @@ -51,7 +52,16 @@ pub async fn process_card(card_from_file: &mut CardFromFile) -> Result<(), Box) { +pub fn get_data_update_interval() -> Result> { + let update_interval = dotenv::var("UPDATE_INTERVAL") + .map_err(|_| "UPDATE_INTERVAL is not defined in the .env file")? + .parse::() + .map_err(|_| "UPDATE_INTERVAL is not a valid number")?; + + Ok(update_interval * 3600) +} + +pub fn process_export_data(cards: &mut Vec) { cards.sort_by(|a, b| b.usd_value.partial_cmp(&a.usd_value).unwrap()); let top_10_cards = &cards[..10]; for card in top_10_cards { @@ -60,4 +70,35 @@ pub fn process_top_cards(cards: &mut Vec) { CARD_VALUES.with_label_values(&[&card.name]).set(value); } } +} + +pub async fn process_cards(interval: &mut tokio::time::Interval) -> Result<(), Box> { + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Please provide the path to the JSON file as an argument."); + return Ok(()); + } + + let file_path = &args[1]; + let file = File::open(file_path)?; + let reader = BufReader::new(file); + let mut cards_data: CardFile = serde_json::from_reader(reader)?; + let pb = util::setup_progress_bar(cards_data.cards.len() as u64); + + for card_from_file in &mut cards_data.cards { + process_card(card_from_file).await?; + pb.inc(1); + interval.tick().await; + } + + process_export_data(&mut cards_data.cards); + pb.finish_with_message("Completed!"); + + let file = OpenOptions::new() + .write(true) + .truncate(true) + .open(file_path)?; + + serde_json::to_writer_pretty(file, &cards_data)?; + Ok(()) } \ No newline at end of file diff --git a/src/handlers.rs b/src/handlers.rs new file mode 100644 index 0000000..c3fc053 --- /dev/null +++ b/src/handlers.rs @@ -0,0 +1,13 @@ +use axum::response::IntoResponse; + +use crate::templates::RootTemplate; +use crate::util::HtmlTemplate; + +pub async fn root() -> impl IntoResponse { + let template = RootTemplate {}; + HtmlTemplate(template) +} + +pub async fn health() -> impl IntoResponse { + "200 OK" +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index db56aef..5846e91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,71 +1,41 @@ use dotenv; -use std::{env, fs::File, fs::OpenOptions, io::BufReader, sync::Arc,net::SocketAddr}; use tokio::time::Duration; -use indicatif::{ProgressBar, ProgressStyle}; use prometheus::Registry; +use tracing::info; +use std::sync::Arc; mod server; -mod card; +mod cards; +mod templates; +mod handlers; +mod util; -use server::run_metrics_server; -use card::{CARD_VALUES, CardFile, process_card, process_top_cards}; +use server::run_server; +use cards::{CARD_VALUES,get_data_update_interval}; #[tokio::main] async fn main() -> Result<(), Box> { dotenv::dotenv().ok(); - - let update_interval = dotenv::var("UPDATE_INTERVAL") - .map_err(|_| "UPDATE_INTERVAL is not defined in the .env file")? - .parse::() - .map_err(|_| "UPDATE_INTERVAL is not a valid number")? - * 3600; - let registry = Arc::new(Registry::new()); - registry.register(Box::new(CARD_VALUES.clone())).unwrap(); + let update_interval = get_data_update_interval()?; + let registry = setup_registry()?; + util::setup_tracing_subscriber(); - let metrics_addr = SocketAddr::from(([127, 0, 0, 1], 3001)); - tokio::spawn(run_metrics_server(metrics_addr, registry.clone())); + let ip_addr = server::get_ip_address()?; + tokio::spawn(run_server(ip_addr, registry.clone())); + info!("setting update interval {}", (update_interval/3600).to_string()); let mut interval = tokio::time::interval(Duration::from_secs(update_interval)); + + info!("server started, now listening on port {}", ip_addr.port()); loop { interval.tick().await; - - let args: Vec = env::args().collect(); - if args.len() < 2 { - eprintln!("Please provide the path to the JSON file as an argument."); - continue; - } - - let file_path = &args[1]; - let file = File::open(file_path)?; - let reader = BufReader::new(file); - let mut cards_data: CardFile = serde_json::from_reader(reader)?; - - let pb = setup_progress_bar(cards_data.cards.len() as u64); - for card_from_file in &mut cards_data.cards { - process_card(card_from_file).await?; - pb.inc(1); - tokio::time::sleep(Duration::from_millis(100)).await; - } - - process_top_cards(&mut cards_data.cards); - pb.finish_with_message("Completed!"); - - let file = OpenOptions::new() - .write(true) - .truncate(true) - .open(file_path)?; - - serde_json::to_writer_pretty(file, &cards_data)?; + cards::process_cards(&mut interval).await?; } } -fn setup_progress_bar(len: u64) -> ProgressBar { - let pb = ProgressBar::new(len); - let style = ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})") - .unwrap() - .progress_chars("#>-"); - pb.set_style(style); - pb -} \ No newline at end of file +fn setup_registry() -> Result, Box> { + let registry = Arc::new(Registry::new()); + registry.register(Box::new(CARD_VALUES.clone())).unwrap(); + Ok(registry) +} diff --git a/src/server.rs b/src/server.rs index 3602ba9..fca8682 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,63 +1,42 @@ -use axum::{routing::get, Router,response::{Html, IntoResponse, Response},http::StatusCode,}; +use axum::{routing::get, Router}; use hyper::{Server, http, body}; +use tower_http::services::ServeDir; use std::net::SocketAddr; use prometheus::{Encoder, TextEncoder, Registry}; use std::sync::Arc; -use tower_http::services::ServeDir; -use askama::Template; -pub async fn run_metrics_server(addr: SocketAddr, registry: Arc) { +use crate::handlers; + +pub async fn run_server(addr: SocketAddr, registry: Arc) { let assets_path = std::env::current_dir().unwrap(); let app = Router::new() - .route("/", get(root)) .nest_service("/assets",ServeDir::new(format!("{}/assets", assets_path.to_str().unwrap()))) - .route("/health", get(|| async { "OK" })) + .route("/", get(handlers::root)) + .route("/health", get(handlers::health)) .route("/metrics", get(move || { let registry = Arc::clone(®istry); async move { let metric_families = registry.gather(); let mut buffer = vec![]; + let encoder = TextEncoder::new(); encoder.encode(&metric_families, &mut buffer).unwrap(); + let metrics = String::from_utf8(buffer).unwrap(); http::Response::new(body::Body::from(metrics)) } })); - Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); + Server::bind(&addr).serve(app.into_make_service()).await.unwrap(); } -async fn root() -> impl IntoResponse { - let template = RootTemplate {}; - HtmlTemplate(template) -} +pub fn get_ip_address() -> Result> { + let ip_addr = SocketAddr::from(([127, 0, 0, 1], + dotenv::var("PORT") + .map_err(|_| "PORT is not defined in the .env file")? + .parse::() + .map_err(|_| "PORT is not a valid number")? + )); -#[derive(Template)] -#[template(path = "root.html")] -struct RootTemplate; - -/// A wrapper type that we'll use to encapsulate HTML parsed by askama into valid HTML for axum to serve. -struct HtmlTemplate(T); - - /// Allows us to convert Askama HTML templates into valid HTML for axum to serve in the response. -impl IntoResponse for HtmlTemplate - where - T: Template, - { - fn into_response(self) -> Response { - // Attempt to render the template with askama - match self.0.render() { - // If we're able to successfully parse and aggregate the template, serve it - Ok(html) => Html(html).into_response(), - // If we're not, return an error or some bit of fallback HTML - Err(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to render template. Error: {}", err), - ) - .into_response(), - } - } - } \ No newline at end of file + Ok(ip_addr) +} \ No newline at end of file diff --git a/src/templates.rs b/src/templates.rs new file mode 100644 index 0000000..4f3803e --- /dev/null +++ b/src/templates.rs @@ -0,0 +1,5 @@ +use askama::Template; + +#[derive(Template)] +#[template(path = "root.html")] +pub struct RootTemplate; \ No newline at end of file diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..8f18a87 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,45 @@ +use axum::{response::{Html,Response},http::StatusCode,response::IntoResponse}; +use askama::Template; +use indicatif::{ProgressBar, ProgressStyle}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + + +/// A wrapper type that we'll use to encapsulate HTML parsed by askama into valid HTML for axum to serve. +pub struct HtmlTemplate(pub T); + + /// Allows us to convert Askama HTML templates into valid HTML for axum to serve in the response. +impl IntoResponse for HtmlTemplate + where + T: Template, + { + fn into_response(self) -> Response { + // Attempt to render the template with askama + match self.0.render() { + // If we're able to successfully parse and aggregate the template, serve it + Ok(html) => Html(html).into_response(), + // If we're not, return an error or some bit of fallback HTML + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to render template. Error: {}", err), + ) + .into_response(), + } + } + } + +pub fn setup_progress_bar(len: u64) -> ProgressBar { + let pb = ProgressBar::new(len); + let style = ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})") + .unwrap() + .progress_chars("#|."); + pb.set_style(style); + pb +} + +pub fn setup_tracing_subscriber() { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "sk_tcg_trader".into()),) + .with(tracing_subscriber::fmt::layer()) + .init(); +} \ No newline at end of file