main.rs 3.2 KB

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