use axum::{extract::State, Json}; use super::{AppError, AppState}; pub async fn get_tonight( State(state): State, ) -> Result, AppError> { let row = sqlx::query("SELECT * FROM tonight WHERE id = 1") .fetch_optional(&state.pool) .await?; match row { Some(r) => { use sqlx::Row; Ok(Json(serde_json::json!({ "date": r.try_get::, _>("date").unwrap_or_default(), "astro_dusk_utc": r.try_get::, _>("astro_dusk_utc").unwrap_or_default(), "astro_dawn_utc": r.try_get::, _>("astro_dawn_utc").unwrap_or_default(), "moon_rise_utc": r.try_get::, _>("moon_rise_utc").unwrap_or_default(), "moon_set_utc": r.try_get::, _>("moon_set_utc").unwrap_or_default(), "moon_illumination": r.try_get::, _>("moon_illumination").unwrap_or_default(), "moon_phase_name": r.try_get::, _>("moon_phase_name").unwrap_or_default(), "moon_ra_deg": r.try_get::, _>("moon_ra_deg").unwrap_or_default(), "moon_dec_deg": r.try_get::, _>("moon_dec_deg").unwrap_or_default(), "true_dark_start_utc": r.try_get::, _>("true_dark_start_utc").unwrap_or_default(), "true_dark_end_utc": r.try_get::, _>("true_dark_end_utc").unwrap_or_default(), "true_dark_minutes": r.try_get::, _>("true_dark_minutes").unwrap_or_default(), "computed_at": r.try_get::, _>("computed_at").unwrap_or_default(), }))) } None => { // Compute live if not cached use crate::astronomy::*; use crate::config::{LAT, LON}; let today = chrono::Utc::now().naive_utc().date(); let (dusk, dawn) = astro_twilight(today, LAT, LON) .map_err(|e| AppError::Internal(e.to_string()))?; let jd = julian_date(dusk + (dawn - dusk) / 2); let (moon_ra, moon_dec) = moon_position(jd); let moon_illum = moon_illumination(jd); let moon_age = moon_age_days(jd); let phase = moon_phase_name(moon_illum, moon_age); Ok(Json(serde_json::json!({ "date": today.to_string(), "astro_dusk_utc": dusk.to_rfc3339(), "astro_dawn_utc": dawn.to_rfc3339(), "moon_illumination": moon_illum, "moon_phase_name": phase, "moon_ra_deg": moon_ra, "moon_dec_deg": moon_dec, }))) } } }