Morning improvements

This commit is contained in:
Andras Schmelczer 2026-03-17 13:29:03 +00:00
parent 3e9fba5303
commit 53fff3efaa
41 changed files with 2438 additions and 637 deletions

View file

@ -35,7 +35,7 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use state::AppState;
use state::{AppState, SharedState};
#[derive(Parser)]
#[command(
@ -366,19 +366,19 @@ async fn main() -> anyhow::Result<()> {
let token_cache = Arc::new(auth::TokenCache::new());
let state = Arc::new(AppState {
let app_state = AppState {
data: property_data,
grid,
h3_cells,
poi_data,
poi_grid,
place_data,
postcode_data,
poi_data: Arc::new(poi_data),
poi_grid: Arc::new(poi_grid),
place_data: Arc::new(place_data),
postcode_data: Arc::new(postcode_data),
feature_name_to_index,
min_keys,
max_keys,
avg_keys,
poi_category_groups,
poi_category_groups: Arc::new(poi_category_groups),
features_response,
screenshot_url: cli.screenshot_url,
public_url: cli.public_url,
@ -397,14 +397,23 @@ async fn main() -> anyhow::Result<()> {
stripe_secret_key: cli.stripe_secret_key,
stripe_webhook_secret: cli.stripe_webhook_secret,
stripe_referral_coupon_id: cli.stripe_referral_coupon_id,
});
};
let shared = Arc::new(SharedState::new(
app_state,
cli.properties,
cli.postcode_features,
cli.listings_buy,
cli.listings_rent,
));
// Start background PocketBase metrics poller (users, saved searches/properties counts)
pocketbase::start_metrics_poller(state.clone());
pocketbase::start_metrics_poller(shared.clone());
let initial_state = shared.load_state();
let cors = CorsLayer::new()
.allow_origin(
state
initial_state
.public_url
.parse::<axum::http::HeaderValue>()
.expect("public_url must be a valid header value"),
@ -413,183 +422,156 @@ async fn main() -> anyhow::Result<()> {
.allow_headers(AllowHeaders::mirror_request())
.allow_credentials(true);
let state_features = state.clone();
let state_hexagons = state.clone();
let state_postcodes = state.clone();
let state_postcode_lookup = state.clone();
let state_pois = state.clone();
let state_poi_categories = state.clone();
let state_hexagon_properties = state.clone();
let state_hexagon_stats = state.clone();
let state_screenshot = state.clone();
let state_export = state.clone();
let state_crawler = state.clone();
let state_pb = state.clone();
let state_postcode_stats = state.clone();
let state_postcode_properties = state.clone();
let state_places = state.clone();
let state_shorten = state.clone();
let state_short_url = state.clone();
let state_ai_filters = state.clone();
let state_streetview = state.clone();
let state_newsletter = state.clone();
let state_travel_modes = state.clone();
let state_travel_destinations = state.clone();
let state_checkout = state.clone();
let state_stripe_webhook = state.clone();
let state_pricing = state.clone();
let state_invites_list = state.clone();
let state_invites_create = state.clone();
let state_invite_get = state.clone();
let state_redeem_invite = state.clone();
let state_journey = state.clone();
let state_telemetry = state.clone();
// 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:
// 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(state_features.clone())),
get(move || routes::get_features(s1.load_state())),
)
.route(
"/api/hexagons",
get(move |ext, query| routes::get_hexagons(state_hexagons.clone(), ext, query)),
get(move |ext, query| routes::get_hexagons(s2.load_state(), ext, query)),
)
.route(
"/api/postcodes",
get(move |ext, query| routes::get_postcodes(state_postcodes.clone(), ext, query)),
get(move |ext, query| routes::get_postcodes(s3.load_state(), ext, query)),
)
.route(
"/api/postcode/{postcode}",
get(move |path| routes::get_postcode_lookup(state_postcode_lookup.clone(), path)),
get(move |path| routes::get_postcode_lookup(s4.load_state(), path)),
)
.route(
"/api/pois",
get(move |query| routes::get_pois(state_pois.clone(), query)),
get(move |query| routes::get_pois(s5.load_state(), query)),
)
.route(
"/api/poi-categories",
get(move || routes::get_poi_categories(state_poi_categories.clone())),
get(move || routes::get_poi_categories(s6.load_state())),
)
.route(
"/api/places",
get(move |query| routes::get_places(state_places.clone(), query)),
get(move |query| routes::get_places(s7.load_state(), query)),
)
.route(
"/api/travel-modes",
get(move || routes::get_travel_modes(state_travel_modes.clone())),
get(move || routes::get_travel_modes(s8.load_state())),
)
.route(
"/api/travel-destinations",
get(move |query| {
routes::get_travel_destinations(state_travel_destinations.clone(), query)
}),
get(move |query| routes::get_travel_destinations(s9.load_state(), query)),
)
.route(
"/api/journey",
get(move |query| routes::get_journey(state_journey.clone(), query)),
get(move |query| routes::get_journey(s10.load_state(), query)),
)
.route(
"/api/hexagon-properties",
get(move |ext, query| {
routes::get_hexagon_properties(state_hexagon_properties.clone(), ext, query)
}),
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(state_hexagon_stats.clone(), ext, query)
}),
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(state_postcode_stats.clone(), ext, query)
}),
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(state_postcode_properties.clone(), ext, query)
}),
get(move |ext, query| routes::get_postcode_properties(s14.load_state(), ext, query)),
)
.route(
"/api/screenshot",
get(move |headers, query| {
routes::get_screenshot(state_screenshot.clone(), headers, query)
}),
get(move |headers, query| routes::get_screenshot(s15.load_state(), headers, query)),
)
.route(
"/api/export",
get(move |headers, ext, query| {
routes::get_export(state_export.clone(), 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(state_shorten.clone(), body)),
post(move |body| routes::post_shorten(s17.load_state(), body)),
)
.route(
"/api/ai-filters",
post(move |ext, body| routes::post_ai_filters(state_ai_filters.clone(), ext, body))
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(state_streetview.clone(), query)),
get(move |query| routes::get_streetview(s19.load_state(), query)),
)
.route(
"/api/newsletter",
patch(move |ext, body| routes::patch_newsletter(state_newsletter.clone(), ext, body)),
patch(move |ext, body| routes::patch_newsletter(s20.load_state(), ext, body)),
)
.route(
"/api/pricing",
get(move || routes::get_pricing(state_pricing.clone())),
get(move || routes::get_pricing(s21.load_state())),
)
.route(
"/api/checkout",
post(move |ext, body| routes::post_checkout(state_checkout.clone(), ext, body))
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(state_stripe_webhook.clone(), headers, body)
}),
post(move |headers, body| routes::post_stripe_webhook(s23.load_state(), headers, body)),
)
.route(
"/api/invites",
get(move |ext| routes::get_invites(state_invites_list.clone(), ext)).post(
move |ext, body| routes::post_invites(state_invites_create.clone(), ext, body),
),
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(state_invite_get.clone(), ext, path)),
get(move |ext, path| routes::get_invite(s26.load_state(), ext, path)),
)
.route(
"/api/redeem-invite",
post(move |ext, body| {
routes::post_redeem_invite(state_redeem_invite.clone(), ext, body)
}),
post(move |ext, body| routes::post_redeem_invite(s27.load_state(), ext, body)),
)
.route(
"/s/{code}",
get(move |path| routes::get_short_url(state_short_url.clone(), path)),
get(move |path| routes::get_short_url(s28.load_state(), path)),
)
.route(
"/api/telemetry",
post(move |ext, headers, body| {
let _ = state_telemetry.clone();
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 = state.public_url.clone();
let public_url_tiles = initial_state.public_url.clone();
let api = api
.route(
"/api/tiles/{z}/{x}/{y}",
@ -609,7 +591,7 @@ async fn main() -> anyhow::Result<()> {
)
.route(
"/pb/{*rest}",
any(move |req| routes::proxy_to_pocketbase(state_pb.clone(), req)),
any(move |req| routes::proxy_to_pocketbase(s_pb.load_state(), req)),
);
let app = if let Some(ref dist) = cli.dist {
@ -621,7 +603,7 @@ async fn main() -> anyhow::Result<()> {
.layer(middleware::from_fn(auth::auth_middleware))
.layer(middleware::from_fn(
move |req: axum::extract::Request, next: middleware::Next| {
let st = state_crawler.clone();
let st = s_crawler.load_state();
async move {
// Inject state into request extensions for auth + OG middleware
let (mut parts, body) = req.into_parts();