use axum::{ extract::{Query, State}, http::StatusCode, response::Json, }; use chrono::NaiveTime; use sqlx::PgPool; use uuid::Uuid; use crate::error::AppError; use crate::models::{DateQuery, MeasurementData, MeasurementView, Unit}; pub async fn index() -> &'static str { "Hello World!" } pub async fn all(State(pool): State) -> Result>, AppError> { let rows = sqlx::query_as!( MeasurementView, "SELECT id, measured_at, metric_type, value, unit FROM measurement" ) .fetch_all(&pool) .await?; Ok(Json(rows)) } pub async fn add( State(pool): State, Json(data): Json, ) -> Result<(StatusCode, Json), AppError> { let rec = sqlx::query!( " INSERT INTO measurement (id, measured_at, metric_type, value, unit) VALUES ($1, $2, $3, $4, $5) RETURNING id ", Uuid::now_v7(), data.measured_at, data.metric_type, data.value, data.unit ) .fetch_one(&pool) .await?; Ok((StatusCode::CREATED, Json(rec.id))) } pub async fn search( State(pool): State, Query(date_query): Query, ) -> Result>, AppError> { let (from, to) = match (date_query.date, date_query.from, date_query.to) { (Some(date), None, None) => (Some(date), Some(date)), (None, from, to) => (from, to), _ => return Err(AppError::BadRequest), }; if from.is_none() && to.is_none() { return Err(AppError::BadRequest); } if let (Some(from), Some(to)) = (from, to) && from > to { return Err(AppError::BadRequest); } let start = from.map(|from| from.and_time(NaiveTime::MIN).and_utc()); let end = match to { Some(to) => { let Some(next_day) = to.succ_opt() else { return Err(AppError::BadRequest); }; Some(next_day.and_time(NaiveTime::MIN).and_utc()) } None => None, }; let rows = sqlx::query_as!( MeasurementView, "SELECT id, measured_at, metric_type, value, unit FROM measurement WHERE ($1::timestamptz IS NULL OR measured_at >= $1) AND ($2::timestamptz IS NULL OR measured_at < $2)", start, end ) .fetch_all(&pool) .await?; Ok(Json(rows)) } pub async fn units(State(pool): State) -> Result>, AppError> { Ok(Json( sqlx::query_scalar("SELECT enum_range(NULL::unit)") .fetch_one(&pool) .await?, )) }