Single-binary self-hosted market watcher for stocks, ETFs, indexes, and futures: live charts, key stats, fundamentals, SEC filings, and SSE streaming.
axumdockerfinancerustself-hostedsqlitestocksvite
1mod app;
2mod compute;
3mod db;
4mod guard;
5mod market;
6mod middleware;
7mod models;
8mod providers;
9mod render;
10mod routes;
11mod scheduler;
12mod seed;
13mod sp500;
14mod stream;
15mod templates;
16mod watchlist;
17
18pub use app::{AppState, Config};
19
20use std::net::SocketAddr;
21
22#[tokio::main]
23async fn main() -> anyhow::Result<()> {
24 dotenvy::dotenv().ok();
25 tracing_subscriber::fmt()
26 .with_env_filter(
27 tracing_subscriber::EnvFilter::try_from_default_env()
28 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info,sqlx=warn")),
29 )
30 .init();
31
32 // Subcommand dispatch; anything else falls through to the HTTP server.
33 let mut args = std::env::args().skip(1);
34 if let Some(cmd) = args.next() {
35 match cmd.as_str() {
36 "seed" => return run_seed().await,
37 "--help" | "-h" => {
38 print_usage();
39 return Ok(());
40 }
41 other => {
42 eprintln!("unknown subcommand: {other}");
43 print_usage();
44 std::process::exit(2);
45 }
46 }
47 }
48
49 serve().await
50}
51
52fn print_usage() {
53 eprintln!(
54 "finance: single-binary axum market-watching app\n\
55 \n\
56 Usage:\n \
57 finance run the HTTP server\n \
58 finance seed (re-)import the curated universe and its history\n"
59 );
60}
61
62async fn run_seed() -> anyhow::Result<()> {
63 let state = AppState::from_env().await?;
64 let client = providers::http::build_client(&state.config);
65 // Yahoo serves deep daily history (one `interval=1d&range=max` call per
66 // symbol) as well as live quotes; it is the app's only price source.
67 let history = providers::yahoo::YahooProvider::new(client);
68 seed::run(&state.pool, &state.config, &history).await
69}
70
71async fn serve() -> anyhow::Result<()> {
72 let port: u16 = std::env::var("PORT")
73 .ok()
74 .and_then(|v| v.parse().ok())
75 .unwrap_or(8000);
76
77 let state = AppState::from_env().await?;
78 scheduler::spawn(state.pool.clone(), state.config.clone(), state.hub.clone());
79 let router = app::router(state);
80
81 let addr = SocketAddr::from(([0, 0, 0, 0], port));
82 let listener = tokio::net::TcpListener::bind(addr).await?;
83 tracing::info!(
84 "finance listening on http://{addr} ({} S&P 500 movers names loaded)",
85 sp500::count()
86 );
87 axum::serve(listener, router).await?;
88 Ok(())
89}