#![feature(iter_intersperse)] use axum::{ extract::State, http::{header, StatusCode, Uri}, response::{Html, IntoResponse}, routing::get, Router, }; use image::ImageFormat; use mysql::prelude::*; use mysql::*; use plotters::prelude::*; use std::io::{BufWriter, Cursor}; use std::sync::Arc; mod my_structs; mod plotting; use crate::my_structs::MyError; use crate::plotting::Plotter; use std::{thread, time}; #[derive(Clone)] struct AppState { sql_pool: Pool, } fn get_sql_pool(url: &str) -> Pool { loop { let sql_pool_attempt = Pool::new(url); if let Ok(sql_pool) = sql_pool_attempt { return sql_pool; } else { println!("Cannot connect to SQL database. Retrying"); thread::sleep(time::Duration::from_millis(1000)); } } } #[tokio::main] async fn main() { //Initialize state let sql_pool = get_sql_pool("mysql://root:hQqjjMa3JbpLrJvJo7FV@db.knyffen.dk:3306/Food"); let shared_state = Arc::new(AppState { sql_pool }); // build our application with a single route let app = Router::new() .route("/get_rows", get(get_rows)) .route("/html_demo", get(html_demo)) .route("/html_plotter", get(html_plotter)) .route("/image.png", get(png_image)) .route("/image.svg", get(svg_image)) .route("/image2.svg", get(svg_image2)) .with_state(shared_state) .fallback(not_found); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } async fn not_found(uri: Uri) -> (StatusCode, String) { (StatusCode::NOT_FOUND, format!("No route for {uri}")) } async fn get_nutrition_rows(sql_pool: Pool) -> Result> { let mut conn = sql_pool .get_conn() .expect("Cannot establish database connection"); let (nutrition_query, nutrition_closure) = my_structs::Nutrition::query_map_helper(); let nutrition_val: Vec = conn .query_map(nutrition_query, nutrition_closure) .expect("Data in database doesn't match the Nutrition class"); Ok(nutrition_val) } async fn get_purchases_rows(sql_pool: Pool) -> Result> { let mut conn = sql_pool .get_conn() .expect("Cannot establish database connection"); let (purchases_query, purchases_closure) = my_structs::Purchase::query_map_helper(); let purchases_val: Vec = conn .query_map(purchases_query, purchases_closure) .expect("Data in database doesn't match the Purchase class"); Ok(purchases_val) } async fn get_rows(State(state): State>) -> Result { let nutrition_val_promise = get_nutrition_rows(state.sql_pool.clone()); let purchases_val_promise = get_purchases_rows(state.sql_pool.clone()); let nutrition_val = nutrition_val_promise.await?; let purchases_val = purchases_val_promise.await?; println!("Serving: get_rows"); Ok(format!("{:#?} {:#?}", nutrition_val, purchases_val)) } async fn html_demo(State(_state): State>) -> Html { println!("Serving: html_demo"); Html("

This is HTML

".to_string()) } fn format_bitmap_as_png(bitmap: Vec, width: u32, height: u32) -> Vec { let image = image::ImageBuffer::, Vec>::from_raw(width, height, bitmap).unwrap(); let mut buffer = BufWriter::new(Cursor::new(Vec::new())); image.write_to(&mut buffer, ImageFormat::Png).unwrap(); let bytes: Vec = buffer.into_inner().unwrap().into_inner(); bytes } fn plot_stuff_bitmap(image_buffer: &mut [u8], width: u32, height: u32) { let root = BitMapBackend::with_buffer(image_buffer, (width, height)).into_drawing_area(); root.fill(&WHITE).unwrap(); let mut chart = ChartBuilder::on(&root) .caption("y=x^2", ("sans-serif", 50).into_font()) .margin(5) .x_label_area_size(30) .y_label_area_size(30) .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32) .unwrap(); chart.configure_mesh().draw().unwrap(); chart .draw_series(LineSeries::new( (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), &RED, )) .unwrap() .label("y = x^2") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); chart .configure_series_labels() .background_style(WHITE.mix(0.8)) .border_style(BLACK) .draw() .unwrap(); root.present().unwrap(); } async fn png_image(State(_state): State>) -> impl IntoResponse { println!("Serving: png_image"); let width: u32 = 640; let height: u32 = 480; let mut image_buffer = vec![0; (width as usize) * (height as usize) * 3]; plot_stuff_bitmap(image_buffer.as_mut_slice(), width, height); let image = format_bitmap_as_png(image_buffer, width, height); let headers = [ (header::CONTENT_TYPE, "image/png"), (header::CONTENT_DISPOSITION, "inline"), ]; (headers, image).into_response() } fn plot_stuff_svg(image_string: &mut String, width: u32, height: u32) { let root = SVGBackend::with_string(image_string, (width, height)).into_drawing_area(); root.fill(&WHITE).unwrap(); let mut chart = ChartBuilder::on(&root) .caption("y=x^2", ("sans-serif", 50).into_font()) .margin(5) .x_label_area_size(30) .y_label_area_size(30) .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32) .unwrap(); chart.configure_mesh().draw().unwrap(); chart .draw_series(LineSeries::new( (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)), &RED, )) .unwrap() .label("y = x^2") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED)); chart .configure_series_labels() .background_style(WHITE.mix(0.8)) .border_style(BLACK) .draw() .unwrap(); root.present().unwrap(); } async fn svg_image(State(_state): State>) -> impl IntoResponse { println!("Serving: svg_image"); let width: u32 = 640; let height: u32 = 480; let mut image_string = String::new(); plot_stuff_svg(&mut image_string, width, height); let headers = [ (header::CONTENT_TYPE, "image/svg+xml"), (header::CONTENT_DISPOSITION, "inline"), ]; (headers, image_string).into_response() } async fn svg_image2(State(_state): State>) -> impl IntoResponse { println!("Serving: svg_image2"); let fun = plotting::SVGPlotter::examplefun(); plotting::SVGPlotter::plot(640, 480, fun) } async fn html_plotter(State(_state): State>) -> Html { println!("Serving: html_plotter"); Html( "

This is HTML

" .to_string(), ) }