refactored project for readability
This commit is contained in:
parent
dc3b994247
commit
aa4559be2a
7 changed files with 149 additions and 95 deletions
3
.env
3
.env
|
|
@ -1 +1,2 @@
|
||||||
UPDATE_INTERVAL=12
|
UPDATE_INTERVAL=12
|
||||||
|
PORT=8080
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
|
||||||
use reqwest;
|
use reqwest;
|
||||||
use prometheus::{GaugeVec, Opts};
|
use prometheus::{GaugeVec, Opts};
|
||||||
|
use std::{env, fs::File, fs::OpenOptions, io::BufReader, collections::HashMap};
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref CARD_VALUES: GaugeVec = GaugeVec::new(
|
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(())
|
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());
|
cards.sort_by(|a, b| b.usd_value.partial_cmp(&a.usd_value).unwrap());
|
||||||
let top_10_cards = &cards[..10];
|
let top_10_cards = &cards[..10];
|
||||||
for card in top_10_cards {
|
for card in top_10_cards {
|
||||||
|
|
@ -60,4 +70,35 @@ pub fn process_top_cards(cards: &mut Vec<CardFromFile>) {
|
||||||
CARD_VALUES.with_label_values(&[&card.name]).set(value);
|
CARD_VALUES.with_label_values(&[&card.name]).set(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
74
src/main.rs
74
src/main.rs
|
|
@ -1,71 +1,41 @@
|
||||||
use dotenv;
|
use dotenv;
|
||||||
use std::{env, fs::File, fs::OpenOptions, io::BufReader, sync::Arc,net::SocketAddr};
|
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
use prometheus::Registry;
|
use prometheus::Registry;
|
||||||
|
use tracing::info;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
mod card;
|
mod cards;
|
||||||
|
mod templates;
|
||||||
|
mod handlers;
|
||||||
|
mod util;
|
||||||
|
|
||||||
use server::run_metrics_server;
|
use server::run_server;
|
||||||
use card::{CARD_VALUES, CardFile, process_card, process_top_cards};
|
use cards::{CARD_VALUES,get_data_update_interval};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
dotenv::dotenv().ok();
|
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 registry = Arc::new(Registry::new());
|
let update_interval = get_data_update_interval()?;
|
||||||
registry.register(Box::new(CARD_VALUES.clone())).unwrap();
|
let registry = setup_registry()?;
|
||||||
|
util::setup_tracing_subscriber();
|
||||||
|
|
||||||
let metrics_addr = SocketAddr::from(([127, 0, 0, 1], 3001));
|
let ip_addr = server::get_ip_address()?;
|
||||||
tokio::spawn(run_metrics_server(metrics_addr, registry.clone()));
|
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));
|
let mut interval = tokio::time::interval(Duration::from_secs(update_interval));
|
||||||
|
|
||||||
|
info!("server started, now listening on port {}", ip_addr.port());
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
|
cards::process_cards(&mut interval).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)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_progress_bar(len: u64) -> ProgressBar {
|
fn setup_registry() -> Result<Arc<Registry>, Box<dyn std::error::Error>> {
|
||||||
let pb = ProgressBar::new(len);
|
let registry = Arc::new(Registry::new());
|
||||||
let style = ProgressStyle::default_bar()
|
registry.register(Box::new(CARD_VALUES.clone())).unwrap();
|
||||||
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
|
Ok(registry)
|
||||||
.unwrap()
|
}
|
||||||
.progress_chars("#>-");
|
|
||||||
pb.set_style(style);
|
|
||||||
pb
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 hyper::{Server, http, body};
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use prometheus::{Encoder, TextEncoder, Registry};
|
use prometheus::{Encoder, TextEncoder, Registry};
|
||||||
use std::sync::Arc;
|
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 assets_path = std::env::current_dir().unwrap();
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(root))
|
|
||||||
.nest_service("/assets",ServeDir::new(format!("{}/assets", assets_path.to_str().unwrap())))
|
.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 || {
|
.route("/metrics", get(move || {
|
||||||
let registry = Arc::clone(®istry);
|
let registry = Arc::clone(®istry);
|
||||||
async move {
|
async move {
|
||||||
let metric_families = registry.gather();
|
let metric_families = registry.gather();
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
|
|
||||||
let encoder = TextEncoder::new();
|
let encoder = TextEncoder::new();
|
||||||
encoder.encode(&metric_families, &mut buffer).unwrap();
|
encoder.encode(&metric_families, &mut buffer).unwrap();
|
||||||
|
|
||||||
let metrics = String::from_utf8(buffer).unwrap();
|
let metrics = String::from_utf8(buffer).unwrap();
|
||||||
http::Response::new(body::Body::from(metrics))
|
http::Response::new(body::Body::from(metrics))
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Server::bind(&addr)
|
Server::bind(&addr).serve(app.into_make_service()).await.unwrap();
|
||||||
.serve(app.into_make_service())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn root() -> impl IntoResponse {
|
pub fn get_ip_address() -> Result<SocketAddr, Box<dyn std::error::Error>> {
|
||||||
let template = RootTemplate {};
|
let ip_addr = SocketAddr::from(([127, 0, 0, 1],
|
||||||
HtmlTemplate(template)
|
dotenv::var("PORT")
|
||||||
}
|
.map_err(|_| "PORT is not defined in the .env file")?
|
||||||
|
.parse::<u16>()
|
||||||
|
.map_err(|_| "PORT is not a valid number")?
|
||||||
|
));
|
||||||
|
|
||||||
#[derive(Template)]
|
Ok(ip_addr)
|
||||||
#[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