| 1 |
|
| 2 |
|
| 3 |
use super::{ui, Ctx}; |
| 4 |
use anyhow::{Context, Result}; |
| 5 |
use spectra_core::ledger::Ledger; |
| 6 |
use spectra_core::principals::Principals; |
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
pub async fn verify(ctx: &Ctx, json: bool) -> Result<i32> { |
| 11 |
let spin = if json { None } else { Some(ui::spinner("verifying the ledger")) }; |
| 12 |
|
| 13 |
let v: serde_json::Value = match ctx.remote()? { |
| 14 |
Some(_) => { |
| 15 |
let home = super::identity::owner_home(ctx, None); |
| 16 |
let c = ctx.owner_client(&home).await?.context("no server")?; |
| 17 |
super::call(&c, "GET", "/api/v1/admin/verify", None).await? |
| 18 |
} |
| 19 |
None => serde_json::to_value(Ledger::open_read_only(&ctx.db()?).await?.verify(&mut Principals::default()).await?)?, |
| 20 |
}; |
| 21 |
if let Some(s) = spin { |
| 22 |
s.clear(); |
| 23 |
} |
| 24 |
let findings = v["findings"].as_array().cloned().unwrap_or_default(); |
| 25 |
let clean = findings.is_empty(); |
| 26 |
if json { |
| 27 |
println!("{v}"); |
| 28 |
return Ok(if clean { 0 } else { 1 }); |
| 29 |
} |
| 30 |
if clean { |
| 31 |
println!("{} {}", ui::done("Verified"), ui::subject("the ledger")); |
| 32 |
} else { |
| 33 |
println!( |
| 34 |
"{} {} {}", |
| 35 |
ui::err(ui::glyph("✘", "x")), |
| 36 |
ui::err("The ledger does not verify:"), |
| 37 |
ui::err(format!("{} finding(s)", findings.len())) |
| 38 |
); |
| 39 |
} |
| 40 |
println!("{}", ui::kv("events", v["events"].as_u64().unwrap_or(0))); |
| 41 |
let last = v["last_checkpoint"].as_u64().map(|s| format!("last at #{s}")).unwrap_or_else(|| "none yet".into()); |
| 42 |
println!("{}", ui::kv("signed", format!("{} checkpoint(s){}{last}", v["checkpoints"].as_u64().unwrap_or(0), ui::sep()))); |
| 43 |
let uncovered = v["uncovered"].as_u64().unwrap_or(0); |
| 44 |
if uncovered > 0 { |
| 45 |
println!("{}", ui::kv("pending", ui::dim(format!("{uncovered} event(s) since, chained and awaiting the next checkpoint")))); |
| 46 |
} |
| 47 |
println!("{}", ui::kv("head", ui::hash(v["head"].as_str().unwrap_or("")))); |
| 48 |
for f in &findings { |
| 49 |
|
| 50 |
let problem = match &f["problem"] { |
| 51 |
serde_json::Value::String(p) => p |
| 52 |
.split('_') |
| 53 |
.map(|w| { |
| 54 |
let mut c = w.chars(); |
| 55 |
c.next().map(|f| f.to_uppercase().chain(c).collect::<String>()).unwrap_or_default() |
| 56 |
}) |
| 57 |
.collect(), |
| 58 |
other => other.to_string(), |
| 59 |
}; |
| 60 |
println!( |
| 61 |
" {} {} {}", |
| 62 |
ui::id(format!("#{}", f["seq"].as_u64().unwrap_or(0))), |
| 63 |
ui::err(problem), |
| 64 |
f["detail"].as_str().unwrap_or("") |
| 65 |
); |
| 66 |
} |
| 67 |
Ok(if clean { 0 } else { 1 }) |
| 68 |
} |
| 69 |
|
| 70 |
|
| 71 |
pub async fn check(ctx: &Ctx) -> Result<i32> { |
| 72 |
ctx.local_only("spectra admin check")?; |
| 73 |
let ledger = ctx.writer().await?; |
| 74 |
let differ = ledger.check().await?; |
| 75 |
if differ.is_empty() { |
| 76 |
println!("{} {}", ui::done("Checked"), ui::subject("every projection matches the ledger")); |
| 77 |
Ok(0) |
| 78 |
} else { |
| 79 |
println!("{} projections differ from the ledger: {}", ui::caution(), ui::err(differ.join(", "))); |
| 80 |
println!(" {} {}", ui::note(), ui::dim("rebuild them with spectra admin rebuild")); |
| 81 |
Ok(1) |
| 82 |
} |
| 83 |
} |
| 84 |
|
| 85 |
|
| 86 |
pub async fn rebuild(ctx: &Ctx) -> Result<i32> { |
| 87 |
ctx.local_only("spectra admin rebuild")?; |
| 88 |
let ledger = ctx.writer().await?; |
| 89 |
let spin = ui::spinner("replaying the ledger"); |
| 90 |
ledger.rebuild().await?; |
| 91 |
spin.done(&format!("{} {}", ui::done("Rebuilt"), ui::subject(format!("projections from {} events", ledger.head().await.0)))); |
| 92 |
Ok(0) |
| 93 |
} |