main.rs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. //! Kuatia dashboard server.
  2. //!
  3. //! Connects to a ledger database, then serves two views of it: a server-rendered
  4. //! HTML dashboard (Tera templates, htmx live refresh) and a read-only JSON REST
  5. //! API under `/api` for anyone who wants to build a richer client.
  6. //!
  7. //! ```sh
  8. //! cargo run -p kuatia-dashboard -- --seed # in-memory demo
  9. //! cargo run -p kuatia-dashboard -- --db sqlite://kuatia.db --port 8080
  10. //! # then open http://127.0.0.1:<port>
  11. //! ```
  12. mod api;
  13. mod assets;
  14. mod data;
  15. mod seed;
  16. mod ui;
  17. use std::error::Error;
  18. use std::sync::Arc;
  19. use axum::Router;
  20. use clap::Parser;
  21. use tower_http::cors::CorsLayer;
  22. use tower_http::trace::TraceLayer;
  23. use data::AppState;
  24. /// Command-line options. Each flag falls back to an environment variable, then
  25. /// a default.
  26. #[derive(Debug, Parser)]
  27. #[command(
  28. name = "kuatia-dashboard",
  29. about = "Server-rendered dashboard and REST API for a Kuatia ledger"
  30. )]
  31. struct Cli {
  32. /// Ledger database URL. The scheme selects the backend: `sqlite::memory:`,
  33. /// `sqlite://path.db`, or `postgres://user:pass@host/db`.
  34. #[arg(long, env = "KUATIA_DASHBOARD_DB", default_value = "sqlite::memory:")]
  35. db: String,
  36. /// Host/interface to bind.
  37. #[arg(long, env = "KUATIA_DASHBOARD_HOST", default_value = "127.0.0.1")]
  38. host: String,
  39. /// TCP port to listen on.
  40. #[arg(long, env = "KUATIA_DASHBOARD_PORT", default_value_t = 3000)]
  41. port: u16,
  42. /// Seed demo accounts and transfers if the ledger is empty. A no-op against
  43. /// an already-populated database.
  44. #[arg(long, env = "KUATIA_DASHBOARD_SEED", default_value_t = false)]
  45. seed: bool,
  46. /// Seed keying the account-code obfuscation. Must be stable across a
  47. /// deployment: changing it changes every rendered account code. Defaults to
  48. /// the built-in `DEFAULT_ID_SEED`.
  49. #[arg(long, env = "KUATIA_ID_SEED")]
  50. id_seed: Option<u64>,
  51. }
  52. #[tokio::main]
  53. async fn main() -> Result<(), Box<dyn Error>> {
  54. tracing_subscriber::fmt()
  55. .with_env_filter(
  56. tracing_subscriber::EnvFilter::try_from_default_env()
  57. .unwrap_or_else(|_| "kuatia_dashboard=info,tower_http=info".into()),
  58. )
  59. .init();
  60. let cli = Cli::parse();
  61. // Set the account-code obfuscation seed before any code is rendered.
  62. if let Some(seed) = cli.id_seed {
  63. kuatia_core::set_id_seed(seed);
  64. }
  65. let ledger = seed::connect(&cli.db).await?;
  66. tracing::info!("connected to ledger at {}", cli.db);
  67. if cli.seed {
  68. if seed::seed_if_empty(&ledger).await? {
  69. tracing::info!("seeded demo ledger");
  70. } else {
  71. tracing::info!("ledger already populated, skipping seed");
  72. }
  73. }
  74. let state = AppState {
  75. ledger,
  76. assets: Arc::new(assets::registry()),
  77. tera: Arc::new(ui::build_tera()?),
  78. };
  79. let app = Router::new()
  80. .merge(ui::router(state.clone()))
  81. .nest("/api", api::router(state))
  82. .layer(TraceLayer::new_for_http())
  83. .layer(CorsLayer::permissive());
  84. let addr = format!("{}:{}", cli.host, cli.port);
  85. let listener = tokio::net::TcpListener::bind(&addr).await?;
  86. tracing::info!("dashboard listening on http://{addr}");
  87. axum::serve(listener, app).await?;
  88. Ok(())
  89. }