use std::path::Path;
use anyhow::{bail, Result};
use laterite_core::config::{BackendConfig, DatabaseConfig};
use serde::Deserialize;
use sqlx::any::AnyPoolOptions;
use crate::new::is_writable;
#[derive(Deserialize)]
struct Config {
database: DatabaseConfig,
#[serde(default)]
backend: BackendConfig,
}
pub async fn run() -> Result<()> {
let config_dir = Path::new("config");
if !config_dir.join("default.toml").exists() {
bail!(
"no config/default.toml here; run `lat doctor` from a Laterite application directory"
);
}
println!("Checking this Laterite application:\n");
let config: Config = match laterite_core::config::load(config_dir, "APP") {
Ok(config) => {
report(true, "Configuration loads");
config
}
Err(err) => {
report(false, "Configuration loads");
println!(" {err}");
bail!("cannot continue without a valid configuration");
}
};
let mut ok = true;
let tz_ok = config.backend.timezone.parse::<chrono_tz::Tz>().is_ok();
ok &= check(
&format!("Timezone '{}' is valid", config.backend.timezone),
tz_ok,
);
let storage = Path::new("storage");
ok &= check(
"storage/ is writable",
storage.is_dir() && is_writable(storage),
);
sqlx::any::install_default_drivers();
let pool = AnyPoolOptions::new()
.max_connections(1)
.connect(&config.database.url)
.await;
match &pool {
Ok(_) => ok &= check("Database is reachable", true),
Err(err) => {
report(false, "Database is reachable");
println!(" {err}");
ok = false;
}
}
if let Ok(pool) = &pool {
for table in ["backend_users", "settings"] {
let exists = sqlx::query(&format!("select 1 from {table} where 1 = 0"))
.fetch_optional(pool)
.await
.is_ok();
ok &= check(&format!("Table '{table}' exists"), exists);
}
}
println!();
if ok {
println!("All checks passed.");
Ok(())
} else {
bail!("some checks failed; see above");
}
}
fn check(label: &str, pass: bool) -> bool {
report(pass, label);
pass
}
fn report(pass: bool, label: &str) {
println!(" {} {label}", if pass { '\u{2713}' } else { '\u{2717}' });
}