refactored project for readability
This commit is contained in:
parent
dc3b994247
commit
aa4559be2a
7 changed files with 149 additions and 95 deletions
1
.env
1
.env
|
|
@ -1 +1,2 @@
|
|||
UPDATE_INTERVAL=12
|
||||
PORT=8080
|
||||
|
|
@ -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<d
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_top_cards(cards: &mut Vec<CardFromFile>) {
|
||||
pub fn get_data_update_interval() -> Result<u64, Box<dyn std::error::Error>> {
|
||||
let update_interval = dotenv::var("UPDATE_INTERVAL")
|
||||
.map_err(|_| "UPDATE_INTERVAL is not defined in the .env file")?
|
||||
.parse::<u64>()
|
||||
.map_err(|_| "UPDATE_INTERVAL is not a valid number")?;
|
||||
|
||||
Ok(update_interval * 3600)
|
||||
}
|
||||
|
||||
pub fn process_export_data(cards: &mut Vec<CardFromFile>) {
|
||||
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 {
|
||||
|
|
@ -61,3 +71,34 @@ pub fn process_top_cards(cards: &mut Vec<CardFromFile>) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process_cards(interval: &mut tokio::time::Interval) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<String> = 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(())
|
||||
}
|
||||
13
src/handlers.rs
Normal file
13
src/handlers.rs
Normal file
|
|
@ -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"
|
||||
}
|
||||
72
src/main.rs
72
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<dyn std::error::Error>> {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
let update_interval = dotenv::var("UPDATE_INTERVAL")
|
||||
.map_err(|_| "UPDATE_INTERVAL is not defined in the .env file")?
|
||||
.parse::<u64>()
|
||||
.map_err(|_| "UPDATE_INTERVAL is not a valid number")?
|
||||
* 3600;
|
||||
let update_interval = get_data_update_interval()?;
|
||||
let registry = setup_registry()?;
|
||||
util::setup_tracing_subscriber();
|
||||
|
||||
let registry = Arc::new(Registry::new());
|
||||
registry.register(Box::new(CARD_VALUES.clone())).unwrap();
|
||||
|
||||
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<String> = 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
|
||||
fn setup_registry() -> Result<Arc<Registry>, Box<dyn std::error::Error>> {
|
||||
let registry = Arc::new(Registry::new());
|
||||
registry.register(Box::new(CARD_VALUES.clone())).unwrap();
|
||||
Ok(registry)
|
||||
}
|
||||
|
|
@ -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<Registry>) {
|
||||
use crate::handlers;
|
||||
|
||||
pub async fn run_server(addr: SocketAddr, registry: Arc<Registry>) {
|
||||
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<SocketAddr, Box<dyn std::error::Error>> {
|
||||
let ip_addr = SocketAddr::from(([127, 0, 0, 1],
|
||||
dotenv::var("PORT")
|
||||
.map_err(|_| "PORT is not defined in the .env file")?
|
||||
.parse::<u16>()
|
||||
.map_err(|_| "PORT is not a valid number")?
|
||||
));
|
||||
|
||||
Ok(ip_addr)
|
||||
}
|
||||
|
||||
#[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>(T);
|
||||
|
||||
/// Allows us to convert Askama HTML templates into valid HTML for axum to serve in the response.
|
||||
impl<T> IntoResponse for HtmlTemplate<T>
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/templates.rs
Normal file
5
src/templates.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use askama::Template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "root.html")]
|
||||
pub struct RootTemplate;
|
||||
45
src/util.rs
Normal file
45
src/util.rs
Normal file
|
|
@ -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<T>(pub T);
|
||||
|
||||
/// Allows us to convert Askama HTML templates into valid HTML for axum to serve in the response.
|
||||
impl<T> IntoResponse for HtmlTemplate<T>
|
||||
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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue