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

@ -1,5 +1,8 @@
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use crate::auth::TokenCache;
@ -10,16 +13,12 @@ use crate::routes::FeaturesResponse;
use crate::utils::GridIndex;
pub struct AppState {
// --- Rebuilt on reload ---
pub data: PropertyData,
pub grid: GridIndex,
/// h3_cells[row_idx] = precomputed H3 cell ID at max resolution (12).
/// Parent cells for lower resolutions derived via CellIndex::parent().
pub h3_cells: Vec<u64>,
pub poi_data: POIData,
pub poi_grid: GridIndex,
pub place_data: PlaceData,
/// Postcode boundary data for high-zoom rendering
pub postcode_data: PostcodeData,
/// O(1) lookup: feature name → index in feature_names/feature_data
pub feature_name_to_index: FxHashMap<String, usize>,
/// Precomputed JSON key names: "min_{feature_name}" for each feature
@ -28,10 +27,25 @@ pub struct AppState {
pub max_keys: Vec<String>,
/// Precomputed JSON key names: "avg_{feature_name}" for each feature
pub avg_keys: Vec<String>,
/// Precomputed POI category groups (sorted)
pub poi_category_groups: Vec<POICategoryGroup>,
/// Precomputed features response for /api/features endpoint
pub features_response: FeaturesResponse,
/// Complete system prompt for AI filters (features + examples + instructions)
pub ai_filters_system_prompt: String,
// --- Shared across reloads (Arc for cheap cloning) ---
pub poi_data: Arc<POIData>,
pub poi_grid: Arc<GridIndex>,
pub place_data: Arc<PlaceData>,
/// Postcode boundary data for high-zoom rendering
pub postcode_data: Arc<PostcodeData>,
/// Precomputed POI category groups (sorted)
pub poi_category_groups: Arc<Vec<POICategoryGroup>>,
/// Precomputed travel time data store
pub travel_time_store: Arc<TravelTimeStore>,
/// Token validation cache (60s TTL)
pub token_cache: Arc<TokenCache>,
// --- Config (cheap to clone) ---
/// URL of the screenshot service (e.g. http://screenshot:8002)
pub screenshot_url: String,
/// Public-facing URL for absolute og:image URLs (e.g. https://perfectpostcodes.dev)
@ -52,12 +66,6 @@ pub struct AppState {
pub gemini_api_key: String,
/// Gemini model name (e.g. gemini-2.0-flash)
pub gemini_model: String,
/// Precomputed travel time data store
pub travel_time_store: Arc<TravelTimeStore>,
/// Token validation cache (60s TTL)
pub token_cache: Arc<TokenCache>,
/// Complete system prompt for AI filters (features + examples + instructions)
pub ai_filters_system_prompt: String,
/// Google Maps API key for Street View metadata lookups
pub google_maps_api_key: String,
/// Stripe secret key for creating checkout sessions
@ -67,3 +75,57 @@ pub struct AppState {
/// Stripe Coupon ID for referral discounts
pub stripe_referral_coupon_id: String,
}
/// Wraps AppState with atomic swap capability for hot-reloading.
/// Route handlers call `load_state()` to get the current snapshot.
/// The reload endpoint builds a new AppState and swaps it in atomically.
pub struct SharedState {
current: RwLock<Arc<AppState>>,
reloading: AtomicBool,
/// Paths needed for data reload
pub properties_path: PathBuf,
pub postcode_features_path: PathBuf,
pub listings_buy_path: PathBuf,
pub listings_rent_path: PathBuf,
}
impl SharedState {
pub fn new(
state: AppState,
properties_path: PathBuf,
postcode_features_path: PathBuf,
listings_buy_path: PathBuf,
listings_rent_path: PathBuf,
) -> Self {
Self {
current: RwLock::new(Arc::new(state)),
reloading: AtomicBool::new(false),
properties_path,
postcode_features_path,
listings_buy_path,
listings_rent_path,
}
}
/// Get the current AppState snapshot. Cheap (Arc clone under a brief read lock).
pub fn load_state(&self) -> Arc<AppState> {
self.current.read().clone()
}
/// Atomically swap in a new AppState. Old state is dropped when all references are gone.
pub fn swap_state(&self, new_state: AppState) {
*self.current.write() = Arc::new(new_state);
}
/// Try to mark reload as in-progress. Returns false if already reloading.
pub fn try_start_reload(&self) -> bool {
self.reloading
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
/// Mark reload as complete.
pub fn finish_reload(&self) {
self.reloading.store(false, Ordering::Release);
}
}