perfect-postcode/server-rs/src/state.rs
2026-05-25 13:20:17 +01:00

108 lines
4.5 KiB
Rust

use std::sync::Arc;
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use crate::auth::TokenCache;
use crate::bugsink::FrontendConfig as BugsinkFrontendConfig;
use crate::data::{
ActualListingData, CrimeByYearData, OutcodeData, POICategoryGroup, POIData, PlaceData,
PostcodeData, PropertyData, TravelTimeStore,
};
use crate::licensing::ShareBoundsCache;
use crate::pocketbase::SuperuserTokenCache;
use crate::routes::FeaturesResponse;
use crate::utils::GridIndex;
pub struct AppState {
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>,
/// 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
pub min_keys: Vec<String>,
/// Precomputed JSON key names: "max_{feature_name}" for each feature
pub max_keys: Vec<String>,
/// Precomputed JSON key names: "avg_{feature_name}" for each feature
pub avg_keys: Vec<String>,
/// 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,
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 outcode centroids for search
pub outcode_data: Arc<OutcodeData>,
/// Precomputed POI category groups (sorted)
pub poi_category_groups: Arc<Vec<POICategoryGroup>>,
/// Precomputed travel time data store
pub travel_time_store: Arc<TravelTimeStore>,
/// Optional real-world listings (e.g. Rightmove / Zoopla data) loaded from ACTUAL_LISTINGS_PATH.
pub actual_listings: Option<Arc<ActualListingData>>,
/// Per-LSOA per-year crime counts used by the right pane to plot trends.
/// Empty when the side parquet was not supplied.
pub crime_by_year: Arc<CrimeByYearData>,
/// Token validation cache (60s TTL)
pub token_cache: Arc<TokenCache>,
/// Cached PocketBase superuser token (10min TTL) to avoid rate-limiting
pub superuser_token_cache: Arc<SuperuserTokenCache>,
/// Cached share-link bbox lookups (5min TTL); used to grant unlicensed
/// users access to the area their share link references.
pub share_cache: Arc<ShareBoundsCache>,
// --- 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)
pub public_url: String,
/// True when --dist is not provided (no static serving, relaxed auth checks)
pub is_dev: bool,
/// Shared HTTP client for proxying to the screenshot service and PocketBase
pub http_client: reqwest::Client,
/// PocketBase server URL for authentication (e.g. http://localhost:8090)
pub pocketbase_url: String,
/// PocketBase superuser email (needed for admin-only operations like subscription updates)
pub pocketbase_admin_email: String,
/// PocketBase superuser password
pub pocketbase_admin_password: String,
/// Gemini API key for AI filters
pub gemini_api_key: String,
/// Gemini model name (e.g. gemini-2.0-flash)
pub gemini_model: String,
/// Google Maps API key for Street View metadata lookups
pub google_maps_api_key: String,
/// Stripe secret key for creating checkout sessions
pub stripe_secret_key: String,
/// Stripe webhook signing secret
pub stripe_webhook_secret: String,
/// Stripe Coupon ID for referral discounts
pub stripe_referral_coupon_id: String,
/// Bugsink/Sentry-compatible browser error reporting config injected into served HTML.
pub bugsink_frontend_config: Option<BugsinkFrontendConfig>,
}
/// Wraps AppState for shared access across route handlers.
/// Route handlers call `load_state()` to get the current snapshot.
pub struct SharedState {
current: RwLock<Arc<AppState>>,
}
impl SharedState {
pub fn new(state: AppState) -> Self {
Self {
current: RwLock::new(Arc::new(state)),
}
}
/// Get the current AppState snapshot. Cheap (Arc clone under a brief read lock).
pub fn load_state(&self) -> Arc<AppState> {
self.current.read().clone()
}
}