pub mod help; pub mod hermes; mod wizard; use crate::{ client::{ApiClient, ApiRequest}, credentials::CredentialStore, error::Result, model::ProfileSelection, }; use serde_json::{Value, json}; use std::sync::Arc; pub use wizard::{SetupOptions, SetupOutcome, SetupUi, run}; pub struct DoctorReport { pub version: Option, pub server_time: Value, pub household_access: bool, pub household_defaults: Value, } pub async fn probe(client: &ApiClient) -> Result { let info = client .request(ApiRequest::get("/system/info")) .await? .json()?; let time = client .request(ApiRequest::get("/system/time")) .await? .json()?; let mut defaults = serde_json::Map::new(); for entity in ["locations", "quantity_units", "shopping_lists"] { let mut r = ApiRequest::get("/objects/{entity}"); r.params.insert("entity".into(), entity.into()); r.query.insert("limit".into(), "100".into()); let rows = client.request(r).await?.json()?; if !rows.is_array() { return Err(crate::error::AppError::new( "INVALID_RESPONSE", "Expected household metadata lists from the API.", )); } defaults.insert(entity.into(), rows); } Ok(DoctorReport { version: info["grocy_version"]["Version"] .as_str() .filter(|s| { s.len() <= 64 && s.chars() .all(|c| c.is_ascii_alphanumeric() || ".-+".contains(c)) }) .map(str::to_owned), server_time: time, household_access: true, household_defaults: Value::Object(defaults), }) } pub async fn doctor(s: ProfileSelection, store: Arc) -> Result { let p = store.load(s).await?; let client = ApiClient::new(p)?; probe(&client).await } pub fn private_defaults(report: &DoctorReport) -> Value { let mut samples = serde_json::Map::new(); for entity in ["locations", "quantity_units", "shopping_lists"] { let rows = report.household_defaults[entity].as_array().map(|rows| rows.iter().take(100).map(|r|json!({"id":r["id"].as_u64().or_else(||r["id"].as_str().and_then(|s|s.parse().ok())),"name":r["name"].as_str().unwrap_or("").chars().take(128).collect::()})).collect::>()).unwrap_or_default(); samples.insert(entity.into(), json!(rows)); } let time = if report.server_time.to_string().len() <= 4096 { report.server_time.clone() } else { Value::Null }; json!({"grocy_version":report.version,"server_time":time,"household_defaults":samples}) }