use axum::{ extract::{Path, Query, State}, http::HeaderMap, response::Html, routing::get, Form, Router, }; use tera::Context; use crate::{ db::{self, DbError, Event, Pagination, Plant}, template::{Template, Update, View}, }; use super::{error::AppError, query::ToQuery}; pub fn app() -> axum::Router { Router::new() .route("/{id}", get(get_plant_page)) .route("/", get(get_plants).post(post_plant)) } async fn get_plant_page( State(ctx): State, headers: HeaderMap, Path(plant_id): Path, ) -> super::Result> { let res = tokio::task::spawn_blocking(move || { let plant = Plant::by_id(&ctx.pool, plant_id)?; if let Some(plant) = plant { let events = Event::for_plant(&ctx.pool, plant.id)?; Ok::<_, DbError>(Some((plant, events))) } else { Ok(None) } }) .await .unwrap()?; match res { Some((plant, events)) => { let mut context = Context::new(); context.insert("plant", &plant); context.insert("events", &events); context.insert("event_types", &db::EVENT_TYPES); Ok(Html( View::Plant.headers(&headers).render(&ctx.tera, &context)?, )) } None => Err(AppError::NotFound), } } async fn get_plants( State(ctx): State, Query(mut page): Query, headers: HeaderMap, ) -> super::Result> { let plants = tokio::task::spawn_blocking(move || db::Plant::page(&ctx.pool, page)) .await .unwrap()?; let mut context = Context::new(); context.insert("plants", &plants); let is_final_page = plants.len() < page.per_page.try_into().unwrap(); context.insert("is_final_page", &is_final_page); if !is_final_page { page.page += 1; context.insert("query", &page.to_query().encode()); } Ok(Html( View::Plants.headers(&headers).render(&ctx.tera, &context)?, )) } async fn post_plant( State(ctx): State, Form(plant): Form, ) -> super::Result> { let plant = tokio::task::spawn_blocking(move || plant.insert(&ctx.pool)) .await .unwrap()?; let mut context = Context::new(); context.insert("plant", &plant); Ok(Html(Update::PlantLi.render(&ctx.tera, &context)?)) }