2024-08-13 20:11:12 +02:00
|
|
|
#![feature(iter_intersperse)]
|
|
|
|
|
|
|
|
use axum::{
|
|
|
|
extract::State,
|
2024-08-13 21:50:17 +02:00
|
|
|
http::{header, StatusCode, Uri},
|
2024-08-13 20:11:12 +02:00
|
|
|
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;
|
2024-08-13 21:50:17 +02:00
|
|
|
use crate::my_structs::MyError;
|
2024-08-13 20:11:12 +02:00
|
|
|
use crate::plotting::Plotter;
|
2024-08-13 21:50:17 +02:00
|
|
|
use std::{thread, time};
|
2024-08-13 20:11:12 +02:00
|
|
|
|
|
|
|
#[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;
|
2024-08-13 21:50:17 +02:00
|
|
|
} else {
|
|
|
|
println!("Cannot connect to SQL database. Retrying");
|
|
|
|
thread::sleep(time::Duration::from_millis(1000));
|
2024-08-13 20:11:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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))
|
2024-08-13 21:50:17 +02:00
|
|
|
.with_state(shared_state)
|
|
|
|
.fallback(not_found);
|
2024-08-13 20:11:12 +02:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2024-08-13 21:50:17 +02:00
|
|
|
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<Vec<my_structs::Nutrition>> {
|
|
|
|
let mut conn = sql_pool
|
|
|
|
.get_conn()
|
|
|
|
.expect("Cannot establish database connection");
|
2024-08-13 20:11:12 +02:00
|
|
|
|
|
|
|
let (nutrition_query, nutrition_closure) = my_structs::Nutrition::query_map_helper();
|
2024-08-13 21:50:17 +02:00
|
|
|
let nutrition_val: Vec<my_structs::Nutrition> = 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<Vec<my_structs::Purchase>> {
|
|
|
|
let mut conn = sql_pool
|
|
|
|
.get_conn()
|
|
|
|
.expect("Cannot establish database connection");
|
2024-08-13 20:11:12 +02:00
|
|
|
|
|
|
|
let (purchases_query, purchases_closure) = my_structs::Purchase::query_map_helper();
|
2024-08-13 21:50:17 +02:00
|
|
|
let purchases_val: Vec<my_structs::Purchase> = 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<Arc<AppState>>) -> Result<String, MyError> {
|
|
|
|
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?;
|
2024-08-13 20:11:12 +02:00
|
|
|
|
|
|
|
println!("Serving: get_rows");
|
2024-08-13 21:50:17 +02:00
|
|
|
Ok(format!("{:#?} {:#?}", nutrition_val, purchases_val))
|
2024-08-13 20:11:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn html_demo(State(_state): State<Arc<AppState>>) -> Html<String> {
|
|
|
|
println!("Serving: html_demo");
|
|
|
|
|
|
|
|
Html("<body><p>This is HTML</p></body>".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_bitmap_as_png(bitmap: Vec<u8>, width: u32, height: u32) -> Vec<u8> {
|
|
|
|
let image =
|
|
|
|
image::ImageBuffer::<image::Rgb<u8>, Vec<u8>>::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<u8> = 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<Arc<AppState>>) -> 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<Arc<AppState>>) -> 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<Arc<AppState>>) -> impl IntoResponse {
|
|
|
|
println!("Serving: svg_image2");
|
|
|
|
let fun = plotting::SVGPlotter::examplefun();
|
|
|
|
plotting::SVGPlotter::plot(640, 480, fun)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn html_plotter(State(_state): State<Arc<AppState>>) -> Html<String> {
|
|
|
|
println!("Serving: html_plotter");
|
|
|
|
|
|
|
|
Html(
|
|
|
|
"<body><p>This is HTML</p><img src=/image.png></img><img src=/image.svg></img></body>"
|
|
|
|
.to_string(),
|
|
|
|
)
|
|
|
|
}
|