calories/server/src/main.rs
2024-09-17 18:40:12 +02:00

248 lines
7.4 KiB
Rust

#![feature(iter_intersperse)]
use axum::{
extract::State,
http::{
// header,
StatusCode,
Uri,
},
response::{Html, IntoResponse},
routing::{get, get_service},
Json, Router,
};
// use image::ImageFormat;
// use mysql::prelude::*;
use mysql::*;
// use plotters::prelude::*;
// use std::io::{BufWriter, Cursor};
use std::sync::Arc;
mod math;
mod my_structs;
use my_structs::MyError;
use my_structs::Purchase;
mod plotting;
// use crate::plotting::Plotter;
use std::{thread, time};
use tower_http::services::ServeDir;
use shared::structs::Nutrition;
#[derive(Clone)]
struct AppState {
sql_pool: Pool,
}
fn get_sql_pool() -> Pool {
let db_user = std::env::var("DB_USER").expect("DB_USER environment variable not set!");
let db_password = std::env::var("DB_PASSWD").expect("DB_PASSWD environment variable not set!");
let db_host = std::env::var("DB_HOST").expect("DB_HOST environment variable not set!");
let db_name = std::env::var("DB_NAME").expect("DB_NAME environment variable not set!");
let url = format!(
"mysql://{}:{}@{}:3306/{}",
db_user, db_password, db_host, db_name
);
loop {
let sql_pool_attempt = Pool::new(url.as_str());
match sql_pool_attempt {
Ok(sql_pool) => return sql_pool,
Err(e) => {
println!("Cannot connect to SQL database. Retrying. {:?}", e);
thread::sleep(time::Duration::from_millis(1000));
}
}
}
}
#[tokio::main]
async fn main() {
//Initialize state
let sql_pool = get_sql_pool();
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("/calorie_intake.svg", get(svg_calorie_intake))
.route("/calorie_intake", get(calorie_intake))
.route("/wasm_test", get(wasm_test))
.route("/camera_test", get(camera_test))
.route("/get_product_options", get(get_product_options))
.nest_service("/pkg", get_service(ServeDir::new("../browser/pkg")))
.nest_service("/css", get_service(ServeDir::new("../css")))
.nest_service("/js", get_service(ServeDir::new("../javascript")))
.nest_service("/assets", get_service(ServeDir::new("../assets")))
.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!("404 not found: {uri}"))
}
async fn get_rows(State(state): State<Arc<AppState>>) -> Result<String, MyError> {
let nutrition_val_promise = Nutrition::get_nutrition_hashmap(state.sql_pool.clone());
let purchases_val_promise = Purchase::get_purchase_rows(state.sql_pool.clone());
let nutrition_val = nutrition_val_promise.await?;
let purchases_val = purchases_val_promise.await?;
println!("Serving: get_rows");
// Ok("See print logs instead".to_string())
Ok(format!("{:#?} {:#?}", nutrition_val, purchases_val))
}
#[axum::debug_handler]
async fn get_product_options(
State(state): State<Arc<AppState>>,
) -> Result<Json<Vec<Nutrition>>, MyError> {
println!("Serving: get_product_options");
let nutrition_val_promise = Nutrition::get_nutrition_rows(state.sql_pool.clone());
let nutrition_val = nutrition_val_promise.await?;
Ok(Json(nutrition_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())
}
async fn svg_image(State(_state): State<Arc<AppState>>) -> impl IntoResponse {
println!("Serving: svg_image");
let fun = plotting::SVGPlotter::examplefun();
plotting::SVGPlotter::plot(640, 480, fun)
}
async fn svg_calorie_intake(
State(state): State<Arc<AppState>>,
) -> Result<impl IntoResponse, MyError> {
println!("Serving: svg_calorie_intake");
let nutrition_val_promise = Nutrition::get_nutrition_hashmap(state.sql_pool.clone());
let purchases_val_promise = Purchase::get_purchase_rows(state.sql_pool.clone());
let nutrition_val = nutrition_val_promise.await?;
let purchases_val = purchases_val_promise.await?;
let plotfun = math::plot_calories_per_day(nutrition_val, purchases_val);
Ok(plotting::SVGPlotter::plot(640, 480, plotfun))
}
async fn png_image(State(_state): State<Arc<AppState>>) -> impl IntoResponse {
println!("Serving: png_image");
let fun = plotting::BitMapPlotter::examplefun();
plotting::BitMapPlotter::plot(640, 480, fun, plotting::BitMapOutputFormat::PNG)
}
async fn html_plotter(State(_state): State<Arc<AppState>>) -> Html<String> {
println!("Serving: html_plotter");
Html("<body><img src=/image.svg></img><img src=/calorie_intake.svg></img></body>".to_string())
}
async fn calorie_intake(State(_state): State<Arc<AppState>>) -> Html<String> {
println!("Serving: calorie_intake");
Html("<body><img src=/calorie_intake.svg></img></body>".to_string())
}
fn construct_js(path: &str) -> Result<String, MyError> {
// let javascript = std::fs::read_to_string(format!("../javascript/{}", path))?;
// let module = format!(
// "
// <script type=\"module\">
// {}
// </script>
// ",
// javascript
// );
// Ok(module)
Ok(format!(
"<script type=\"module\" src=\"/js/{}\"></script>",
path
))
}
fn construct_css(path: &str) -> Result<String, MyError> {
Ok(format!("<link rel=\"stylesheet\" href=\"/css/{}\"/>", path))
}
fn construct_tmpl(path: &str) -> Result<String, MyError> {
Ok(std::fs::read_to_string(format!("../templates/{}", path))?)
}
fn construct_html(
js_paths: Vec<&str>,
css_paths: Vec<&str>,
tmpl_paths: Vec<&str>,
) -> Result<String, MyError> {
let js_modules: Vec<String> = js_paths
.into_iter()
.map(construct_js)
.collect::<Result<Vec<String>, MyError>>()?;
let css_styling: Vec<String> = css_paths
.into_iter()
.map(construct_css)
.collect::<Result<Vec<String>, MyError>>()?;
let tmpl_snippets: Vec<String> = tmpl_paths
.into_iter()
.map(construct_tmpl)
.collect::<Result<Vec<String>, MyError>>()?;
let html = format!(
"
<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
{}
{}
</head>
<body>
{}
</body>
",
js_modules.join(""),
css_styling.join(""),
tmpl_snippets.join("")
);
Ok(html)
}
fn get_template(path: &str) -> Result<String, MyError> {
Ok(std::fs::read_to_string(format!("../templates/{}", path))?)
}
async fn wasm_test(State(_state): State<Arc<AppState>>) -> Result<Html<String>, MyError> {
println!("Serving: wasm_test");
let content = get_template("view_calories.html")?;
Ok(Html(content))
}
async fn camera_test(State(_state): State<Arc<AppState>>) -> Result<Html<String>, MyError> {
println!("Serving: camera_test");
let html = construct_html(
vec!["camera.js"],
vec!["camera_test.css"],
vec!["camera_test.html"],
)?;
Ok(Html(html))
}