summaryrefslogtreecommitdiff
path: root/src/handlers.rs
blob: 3f815a5e2164f53c7846e929c4cb8a88d0a3d9a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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<PgPool>) -> Result<Json<Vec<MeasurementView>>, 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<PgPool>,
    Json(data): Json<MeasurementData>,
) -> Result<(StatusCode, Json<Uuid>), 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<PgPool>,
    Query(date_query): Query<DateQuery>,
) -> Result<Json<Vec<MeasurementView>>, 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<PgPool>) -> Result<Json<Vec<Unit>>, AppError> {
    Ok(Json(
        sqlx::query_scalar("SELECT enum_range(NULL::unit)")
            .fetch_one(&pool)
            .await?,
    ))
}