1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112use std::{
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::Context;
use axum::{Router, routing::get};
use chrono::Utc;
use config::Config;
use forecast::{TemplateForecast, get_forecast};
use tera::Tera;
use tokio::{
sync::{Notify, RwLock},
task,
};
use tower_http::{services::ServeDir, trace::TraceLayer};
use tracing_subscriber::EnvFilter;
use worker::background_worker;
mod config;
mod filters;
mod forecast;
mod routes;
mod worker;
const MIN_UPDATE_FREQUENCY: i64 = 300;
#[derive(Default)]
struct ForecastCache {
forecasts: Vec<TemplateForecast>,
updated_at: i64,
}
impl ForecastCache {
fn update(&mut self, new_forecasts: Vec<TemplateForecast>) {
self.forecasts.clear();
self.forecasts.extend(new_forecasts);
self.updated_at = Utc::now().timestamp()
}
fn needs_update(&self) -> bool {
Utc::now().timestamp() - self.updated_at > MIN_UPDATE_FREQUENCY
}
}
#[derive(Clone, Default)]
struct AppState {
tera: Tera,
forecast_cache: Arc<RwLock<ForecastCache>>,
config: Config,
notify: Arc<Notify>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Setup tracing
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.or_else(|_| {
EnvFilter::try_new("server=debug,tower_http=debug,axum::rejection=trace")
})
.unwrap(),
)
.init();
// Load config
let server_dir = Path::new("server");
let path = if server_dir.is_dir() {
PathBuf::from("server")
} else {
PathBuf::new()
};
let config = Config::new(path.join("config.toml")).context("Failed to load config.toml")?;
// Load templates
let mut tera = Tera::new(
path.join("templates/**/*.tera")
.to_str()
.context("Failed to create template glob")?,
)
.context("Failed to load templates")?;
filters::register_all(&mut tera);
// Setup axum
let state = AppState {
tera,
config: config.clone(),
..Default::default()
};
task::spawn(background_worker(state.clone()));
let static_files = ServeDir::new(path.join("static"));
let app = Router::new()
.route("/", get(routes::index))
.route("/is_data_fresh", get(routes::is_data_fresh))
.nest_service("/static", static_files)
.with_state(state)
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind(config.listen_addr).await?;
axum::serve(listener, app).await?;
Ok(())
}