Initial commit
This commit is contained in:
commit
565f1c497e
3242
Cargo.lock
generated
Normal file
3242
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "calories"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
axum = "0.7.5"
|
||||
chrono = "0.4.38"
|
||||
image = "0.25.2"
|
||||
mysql = "25.0.1"
|
||||
mysql_common = { version = "0.32.4", features = ["frunk"] }
|
||||
plotters = "0.3.6"
|
||||
plotters-canvas = "0.3.0"
|
||||
struct-field-names-as-array = "0.3.0"
|
||||
time = "0.3.36"
|
||||
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread"] }
|
||||
wasm-bindgen = "0.2.92"
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
194
src/main.rs
Normal file
194
src/main.rs
Normal file
@ -0,0 +1,194 @@
|
||||
#![feature(iter_intersperse)]
|
||||
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::header,
|
||||
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::plotting::Plotter;
|
||||
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
||||
// 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 get_rows(State(state): State<Arc<AppState>>) -> String {
|
||||
let mut conn = state.sql_pool.get_conn().unwrap();
|
||||
|
||||
let (nutrition_query, nutrition_closure) = my_structs::Nutrition::query_map_helper();
|
||||
let nutrition_val: Vec<my_structs::Nutrition> =
|
||||
conn.query_map(nutrition_query, nutrition_closure).unwrap();
|
||||
|
||||
let (purchases_query, purchases_closure) = my_structs::Purchase::query_map_helper();
|
||||
let purchases_val: Vec<my_structs::Purchase> =
|
||||
conn.query_map(purchases_query, purchases_closure).unwrap();
|
||||
|
||||
println!("Serving: get_rows");
|
||||
format!("{:#?} {:#?}", nutrition_val, purchases_val)
|
||||
}
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
4
src/my_structs.rs
Normal file
4
src/my_structs.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub use self::nutrition::Nutrition;
|
||||
pub use self::purchases::Purchase;
|
||||
mod nutrition;
|
||||
mod purchases;
|
105
src/my_structs/nutrition.rs
Normal file
105
src/my_structs/nutrition.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use mysql_common::frunk::{hlist_pat, HList};
|
||||
use struct_field_names_as_array::FieldNamesAsArray;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, PartialEq, FieldNamesAsArray)]
|
||||
pub struct Nutrition {
|
||||
id: i32,
|
||||
name: String,
|
||||
manufacturer: String,
|
||||
barcode: String,
|
||||
amount: i32,
|
||||
divisor: i32,
|
||||
kJ: f32,
|
||||
kcal: f32,
|
||||
saturated_fat: f32,
|
||||
carbohydrate: f32,
|
||||
sugar: f32,
|
||||
fibres: f32,
|
||||
protein: f32,
|
||||
salt: f32,
|
||||
vitamin_b2: f32,
|
||||
vitamin_b12: f32,
|
||||
calcium: f32,
|
||||
phosphor: f32,
|
||||
}
|
||||
|
||||
type RowType = HList!(
|
||||
i32,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<i32>,
|
||||
Option<i32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
);
|
||||
|
||||
impl Nutrition {
|
||||
fn get_sql_fields() -> String {
|
||||
Nutrition::FIELD_NAMES_AS_ARRAY
|
||||
.iter()
|
||||
.cloned()
|
||||
.intersperse(", ")
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn query_map_helper() -> (String, impl Fn(RowType) -> Nutrition) {
|
||||
let sql_query = format!("SELECT {} from nutrition", Self::get_sql_fields());
|
||||
|
||||
let construction_closure = |row: RowType| {
|
||||
let hlist_pat![
|
||||
id,
|
||||
name,
|
||||
manufacturer,
|
||||
barcode,
|
||||
amount,
|
||||
divisor,
|
||||
k_j,
|
||||
kcal,
|
||||
saturated_fat,
|
||||
carbohydrate,
|
||||
sugar,
|
||||
fibres,
|
||||
protein,
|
||||
salt,
|
||||
vitamin_b2,
|
||||
vitamin_b12,
|
||||
calcium,
|
||||
phosphor
|
||||
] = row;
|
||||
Nutrition {
|
||||
id,
|
||||
name: name.unwrap_or("".to_string()),
|
||||
manufacturer: manufacturer.unwrap_or("".to_string()),
|
||||
barcode: barcode.unwrap_or("".to_string()),
|
||||
amount: amount.unwrap_or(0),
|
||||
divisor: divisor.unwrap_or(0),
|
||||
kJ: k_j.unwrap_or(0.),
|
||||
kcal: kcal.unwrap_or(0.),
|
||||
saturated_fat: saturated_fat.unwrap_or(0.),
|
||||
carbohydrate: carbohydrate.unwrap_or(0.),
|
||||
sugar: sugar.unwrap_or(0.),
|
||||
fibres: fibres.unwrap_or(0.),
|
||||
protein: protein.unwrap_or(0.),
|
||||
salt: salt.unwrap_or(0.),
|
||||
vitamin_b2: vitamin_b2.unwrap_or(0.),
|
||||
vitamin_b12: vitamin_b12.unwrap_or(0.),
|
||||
calcium: calcium.unwrap_or(0.),
|
||||
phosphor: phosphor.unwrap_or(0.),
|
||||
}
|
||||
};
|
||||
|
||||
(sql_query, construction_closure)
|
||||
}
|
||||
}
|
41
src/my_structs/purchases.rs
Normal file
41
src/my_structs/purchases.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use mysql_common::frunk::{hlist_pat, HList};
|
||||
use struct_field_names_as_array::FieldNamesAsArray;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, PartialEq, FieldNamesAsArray)]
|
||||
pub struct Purchase {
|
||||
id: u32,
|
||||
nutrition_id: u32,
|
||||
amount: u32,
|
||||
datetime: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
type RowType = HList!(u32, u32, u32, PrimitiveDateTime);
|
||||
|
||||
impl Purchase {
|
||||
fn get_sql_fields() -> String {
|
||||
Purchase::FIELD_NAMES_AS_ARRAY
|
||||
.iter()
|
||||
.cloned()
|
||||
.intersperse(", ")
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn query_map_helper() -> (String, impl Fn(RowType) -> Purchase) {
|
||||
let sql_query = format!("SELECT {} from purchases", Self::get_sql_fields());
|
||||
println!("{}", sql_query);
|
||||
|
||||
let construction_closure = |row: RowType| {
|
||||
let hlist_pat![id, nutrition_id, amount, datetime] = row;
|
||||
Purchase {
|
||||
id,
|
||||
nutrition_id,
|
||||
amount,
|
||||
datetime,
|
||||
}
|
||||
};
|
||||
|
||||
(sql_query, construction_closure)
|
||||
}
|
||||
}
|
4
src/plotting.rs
Normal file
4
src/plotting.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub use self::plotter::Plotter;
|
||||
pub use self::svg_plotter::SVGPlotter;
|
||||
mod plotter;
|
||||
mod svg_plotter;
|
21
src/plotting/plotter.rs
Normal file
21
src/plotting/plotter.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use axum::response::IntoResponse;
|
||||
use plotters::prelude::*;
|
||||
|
||||
pub trait Plotter {
|
||||
//fn plot(width: u32, height: u32, plotting_fun: &dyn Fn(IntoDrawingArea)) -> impl IntoResponse;
|
||||
fn plot(
|
||||
width: u32,
|
||||
height: u32,
|
||||
//plotting_fun: &dyn Fn(Box<dyn IntoDrawingArea>),
|
||||
plotting_fun: Box<
|
||||
dyn Fn(
|
||||
plotters::drawing::DrawingArea<
|
||||
plotters::backend::SVGBackend,
|
||||
plotters::coord::Shift,
|
||||
>,
|
||||
),
|
||||
>,
|
||||
) -> impl IntoResponse;
|
||||
//fn plot(width: u32, height: u32, plotting_fun: &dyn Fn()) -> impl IntoResponse;
|
||||
//fn plot(width: u32, height: u32) -> impl IntoResponse;
|
||||
}
|
62
src/plotting/svg_plotter.rs
Normal file
62
src/plotting/svg_plotter.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use super::plotter::Plotter;
|
||||
use axum::http::header;
|
||||
use axum::response::IntoResponse;
|
||||
use plotters::prelude::*;
|
||||
|
||||
pub struct SVGPlotter {}
|
||||
pub type SVGPlotRoot<'a> =
|
||||
plotters::drawing::DrawingArea<plotters::backend::SVGBackend<'a>, plotters::coord::Shift>;
|
||||
pub type SVGPlotFun = Box<dyn Fn(SVGPlotRoot)>;
|
||||
|
||||
impl SVGPlotter {
|
||||
pub fn examplefun() -> SVGPlotFun {
|
||||
Box::new(|root: SVGPlotRoot| {
|
||||
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();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Plotter for SVGPlotter {
|
||||
fn plot(width: u32, height: u32, plotting_fun: SVGPlotFun) -> impl IntoResponse {
|
||||
let mut image_string = String::new();
|
||||
|
||||
{
|
||||
let root =
|
||||
SVGBackend::with_string(&mut image_string, (width, height)).into_drawing_area();
|
||||
plotting_fun(root);
|
||||
}
|
||||
|
||||
let headers = [
|
||||
(header::CONTENT_TYPE, "image/svg+xml"),
|
||||
(header::CONTENT_DISPOSITION, "inline"),
|
||||
];
|
||||
(headers, image_string).into_response()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user