Shared state

This commit is contained in:
Andras Schmelczer 2026-03-17 21:08:32 +00:00
parent 53fff3efaa
commit 15fa09430b
25 changed files with 174 additions and 215 deletions

View file

@ -422,157 +422,78 @@ async fn main() -> anyhow::Result<()> {
.allow_headers(AllowHeaders::mirror_request())
.allow_credentials(true);
// Each route closure captures a clone of `shared` and calls `load_state()`
// at request time to get the latest `Arc<AppState>`. This enables hot-reload:
// Handlers use Axum's State extractor to get Arc<SharedState>, then call
// load_state() to get the current Arc<AppState>. This enables hot-reload:
// the reload endpoint swaps in a new AppState, and subsequent requests pick it up.
macro_rules! s {
() => {
shared.clone()
};
}
let (s1, s2, s3, s4, s5, s6) = (s!(), s!(), s!(), s!(), s!(), s!());
let (s7, s8, s9, s10, s11, s12) = (s!(), s!(), s!(), s!(), s!(), s!());
let (s13, s14, s15, s16, s17, s18) = (s!(), s!(), s!(), s!(), s!(), s!());
let (s19, s20, s21, s22, s23, s24) = (s!(), s!(), s!(), s!(), s!(), s!());
let (s25, s26, s27, s28, s29) = (s!(), s!(), s!(), s!(), s!());
let s_crawler = shared.clone();
let s_pb = shared.clone();
let s_reload = shared.clone();
let api = Router::new()
.route(
"/api/features",
get(move || routes::get_features(s1.load_state())),
)
.route(
"/api/hexagons",
get(move |ext, query| routes::get_hexagons(s2.load_state(), ext, query)),
)
.route(
"/api/postcodes",
get(move |ext, query| routes::get_postcodes(s3.load_state(), ext, query)),
)
.route(
"/api/postcode/{postcode}",
get(move |path| routes::get_postcode_lookup(s4.load_state(), path)),
)
.route(
"/api/pois",
get(move |query| routes::get_pois(s5.load_state(), query)),
)
.route(
"/api/poi-categories",
get(move || routes::get_poi_categories(s6.load_state())),
)
.route(
"/api/places",
get(move |query| routes::get_places(s7.load_state(), query)),
)
.route(
"/api/travel-modes",
get(move || routes::get_travel_modes(s8.load_state())),
)
.route(
"/api/travel-destinations",
get(move |query| routes::get_travel_destinations(s9.load_state(), query)),
)
.route(
"/api/journey",
get(move |query| routes::get_journey(s10.load_state(), query)),
)
.route(
"/api/hexagon-properties",
get(move |ext, query| routes::get_hexagon_properties(s11.load_state(), ext, query)),
)
.route(
"/api/hexagon-stats",
get(move |ext, query| routes::get_hexagon_stats(s12.load_state(), ext, query)),
)
.route(
"/api/postcode-stats",
get(move |ext, query| routes::get_postcode_stats(s13.load_state(), ext, query)),
)
.route(
"/api/postcode-properties",
get(move |ext, query| routes::get_postcode_properties(s14.load_state(), ext, query)),
)
.route(
"/api/screenshot",
get(move |headers, query| routes::get_screenshot(s15.load_state(), headers, query)),
)
.route(
"/api/export",
get(move |headers, ext, query| {
routes::get_export(s16.load_state(), headers, ext, query)
})
.layer(ConcurrencyLimitLayer::new(3)),
)
.route("/api/me", get(routes::get_me))
.route(
"/api/shorten",
post(move |body| routes::post_shorten(s17.load_state(), body)),
)
.route(
"/api/ai-filters",
post(move |ext, body| routes::post_ai_filters(s18.load_state(), ext, body))
.layer(ConcurrencyLimitLayer::new(5)),
)
.route(
"/api/streetview",
get(move |query| routes::get_streetview(s19.load_state(), query)),
)
.route(
"/api/newsletter",
patch(move |ext, body| routes::patch_newsletter(s20.load_state(), ext, body)),
)
.route(
"/api/pricing",
get(move || routes::get_pricing(s21.load_state())),
)
.route(
"/api/checkout",
post(move |ext, body| routes::post_checkout(s22.load_state(), ext, body))
.layer(ConcurrencyLimitLayer::new(10)),
)
.route(
"/api/stripe-webhook",
post(move |headers, body| routes::post_stripe_webhook(s23.load_state(), headers, body)),
)
.route(
"/api/invites",
get(move |ext| routes::get_invites(s24.load_state(), ext))
.post(move |ext, body| routes::post_invites(s25.load_state(), ext, body)),
)
.route(
"/api/invite/{code}",
get(move |ext, path| routes::get_invite(s26.load_state(), ext, path)),
)
.route(
"/api/redeem-invite",
post(move |ext, body| routes::post_redeem_invite(s27.load_state(), ext, body)),
)
.route(
"/s/{code}",
get(move |path| routes::get_short_url(s28.load_state(), path)),
)
.route(
"/api/telemetry",
post(move |ext, headers, body| {
let _ = s29.load_state();
routes::post_telemetry(ext, headers, body)
}),
)
.route(
"/api/reload",
post(move || routes::post_reload(s_reload.clone())),
);
// Add tile routes
let reader_tile = tile_reader.clone();
let reader_style = tile_reader.clone();
let public_url_tiles = initial_state.public_url.clone();
let api = api
let api = Router::new()
.route("/api/features", get(routes::get_features))
.route("/api/hexagons", get(routes::get_hexagons))
.route("/api/postcodes", get(routes::get_postcodes))
.route(
"/api/postcode/{postcode}",
get(routes::get_postcode_lookup),
)
.route("/api/pois", get(routes::get_pois))
.route("/api/poi-categories", get(routes::get_poi_categories))
.route("/api/places", get(routes::get_places))
.route("/api/travel-modes", get(routes::get_travel_modes))
.route(
"/api/travel-destinations",
get(routes::get_travel_destinations),
)
.route("/api/journey", get(routes::get_journey))
.route(
"/api/hexagon-properties",
get(routes::get_hexagon_properties),
)
.route("/api/hexagon-stats", get(routes::get_hexagon_stats))
.route("/api/postcode-stats", get(routes::get_postcode_stats))
.route(
"/api/postcode-properties",
get(routes::get_postcode_properties),
)
.route("/api/screenshot", get(routes::get_screenshot))
.route(
"/api/export",
get(routes::get_export).layer(ConcurrencyLimitLayer::new(3)),
)
.route("/api/me", get(routes::get_me))
.route("/api/shorten", post(routes::post_shorten))
.route(
"/api/ai-filters",
post(routes::post_ai_filters).layer(ConcurrencyLimitLayer::new(5)),
)
.route("/api/streetview", get(routes::get_streetview))
.route("/api/newsletter", patch(routes::patch_newsletter))
.route("/api/pricing", get(routes::get_pricing))
.route(
"/api/checkout",
post(routes::post_checkout).layer(ConcurrencyLimitLayer::new(10)),
)
.route(
"/api/stripe-webhook",
post(routes::post_stripe_webhook),
)
.route(
"/api/invites",
get(routes::get_invites).post(routes::post_invites),
)
.route("/api/invite/{code}", get(routes::get_invite))
.route("/api/redeem-invite", post(routes::post_redeem_invite))
.route("/s/{code}", get(routes::get_short_url))
.route("/api/telemetry", post(routes::post_telemetry))
.route("/api/reload", post(routes::post_reload))
.route(
"/pb/{*rest}",
any(routes::proxy_to_pocketbase),
)
// Tile routes use a different state type — kept as closures
.route(
"/api/tiles/{z}/{x}/{y}",
get(move |path| routes::get_tile(axum::extract::State(reader_tile.clone()), path)),
@ -589,10 +510,7 @@ async fn main() -> anyhow::Result<()> {
"/metrics",
get(move || metrics::metrics_handler(metrics_handle.clone())),
)
.route(
"/pb/{*rest}",
any(move |req| routes::proxy_to_pocketbase(s_pb.load_state(), req)),
);
.with_state(shared.clone());
let app = if let Some(ref dist) = cli.dist {
api.fallback_service(ServeDir::new(dist).fallback(ServeFile::new(dist.join("index.html"))))