use axum::{ Router, extract::State, http::{Response, StatusCode}, response::Json, routing::{get, post}, }; use chrono::{DateTime, Utc}; use dotenvy::dotenv; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{Value, from_value, json}; use sqlx::{PgPool, postgres::PgPoolOptions}; use std::env; use uuid::Uuid; const DATABASE_URL_FALLBACK: &str = "postgres://health:health@127.0.0.1:5432/health"; #[derive(sqlx::Type, Serialize, Deserialize)] #[sqlx(type_name = "metric_type")] pub enum MetricType { BodyWeight, Hip, Waist, } #[derive(sqlx::Type, Serialize, Deserialize)] #[sqlx(type_name = "unit")] pub enum Unit { Kilogram, Centimeter, } #[derive(Serialize, Deserialize)] pub struct Row { id: Uuid, measured_at: DateTime, metric_type: MetricType, value: f64, unit: Unit, } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); dotenv().ok(); let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| DATABASE_URL_FALLBACK.to_owned()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&database_url) .await?; let app = Router::new() .route("/", get(index)) .route("/all", get(all)) .route("/add", post(add)) .with_state(pool); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; tracing::info!("listening at 0.0.0.0:3000"); axum::serve(listener, app).await?; Ok(()) } async fn index() -> &'static str { "Hello World!" } async fn all(State(pool): State) -> Json { let rows = sqlx::query_as!(Row, "SELECT * FROM measurement") .fetch_all(&pool) .await .unwrap_or_else(|_| Vec::new()); Json(json!(rows)) } async fn add(State(pool): State, Json(row): Json) -> StatusCode { if let Ok(insert_row) = from_value::(row) { match sqlx::query!("INSERT INTO measurement (id, measured_at, metric_type, value, unit) VALUES ($1, $2, $3, $4, $5)", insert_row.id, insert_row.measured_at, insert_row.metric_type, insert_row.value, insert_row.unit).execute(&pool).await { Ok(_) => {return StatusCode::CREATED;}, Err(sqlx::Error::Database(e)) if e.is_unique_violation() => StatusCode::CONFLICT, Err(_) => StatusCode::INTERNAL_SERVER_ERROR } } else { return StatusCode::UNPROCESSABLE_ENTITY; } }