This commit is contained in:
Andras Schmelczer 2026-02-18 21:22:15 +00:00
parent 524580eb25
commit ffe080adef
82 changed files with 2652 additions and 2956 deletions

View file

@ -28,6 +28,8 @@ use tower_http::cors::{AllowHeaders, AllowMethods, CorsLayer};
use tower_http::services::{ServeDir, ServeFile};
use tower_http::trace::TraceLayer;
use tracing::info;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use state::AppState;
@ -35,9 +37,21 @@ use state::AppState;
#[derive(Parser)]
#[command(name = "perfect-postcode", about = "Perfect Postcode property map server")]
struct Cli {
/// Path to the wide property parquet file
/// Path to properties.parquet (one row per historical property)
#[arg(long)]
data: PathBuf,
properties: PathBuf,
/// Path to postcode.parquet (one row per postcode with area-level data)
#[arg(long)]
postcode_features: PathBuf,
/// Path to online_listings_buy.parquet
#[arg(long)]
listings_buy: PathBuf,
/// Path to online_listings_rent.parquet
#[arg(long)]
listings_rent: PathBuf,
/// Path to the POI parquet file
#[arg(long)]
@ -79,11 +93,11 @@ struct Cli {
#[arg(long, env = "POCKETBASE_ADMIN_PASSWORD")]
pocketbase_admin_password: String,
/// Ollama server URL for AI area summaries (e.g. http://ollama:11434)
/// Ollama server URL (e.g. http://ollama:11434)
#[arg(long, env = "OLLAMA_URL")]
ollama_url: String,
/// Ollama model name for area summaries
/// Ollama model name
#[arg(long, env = "OLLAMA_MODEL")]
ollama_model: String,
@ -115,22 +129,24 @@ struct Cli {
#[arg(long, env = "GOOGLE_OAUTH_CLIENT_SECRET")]
google_oauth_client_secret: String,
/// Apple OAuth client ID for PocketBase SSO
#[arg(long, env = "APPLE_OAUTH_CLIENT_ID")]
apple_oauth_client_id: String,
/// Apple OAuth client secret for PocketBase SSO
#[arg(long, env = "APPLE_OAUTH_CLIENT_SECRET")]
apple_oauth_client_secret: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
let file_appender = tracing_appender::rolling::daily("logs", "server.log");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::registry()
.with(env_filter)
.with(tracing_subscriber::fmt::layer().with_ansi(true))
.with(
tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_writer(non_blocking),
)
.with_ansi(true)
.init();
// Initialize Prometheus metrics
@ -139,16 +155,30 @@ async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let parquet_path = &cli.data;
if !parquet_path.exists() {
bail!(
"Property parquet file not found: {}",
parquet_path.display()
);
for (label, path) in [
("Properties", &cli.properties),
("Postcode features", &cli.postcode_features),
("Listings buy", &cli.listings_buy),
("Listings rent", &cli.listings_rent),
] {
if !path.exists() {
bail!("{} parquet file not found: {}", label, path.display());
}
}
info!("Loading property data from {}", parquet_path.display());
let property_data = data::PropertyData::load(parquet_path)?;
info!(
"Loading property data from {}, {}, {}, {}",
cli.properties.display(),
cli.postcode_features.display(),
cli.listings_buy.display(),
cli.listings_rent.display(),
);
let property_data = data::PropertyData::load(
&cli.properties,
&cli.postcode_features,
&cli.listings_buy,
&cli.listings_rent,
)?;
info!(
rows = property_data.lat.len(),
features = property_data.num_features,
@ -297,8 +327,6 @@ async fn main() -> anyhow::Result<()> {
&cli.public_url,
&cli.google_oauth_client_id,
&cli.google_oauth_client_secret,
&cli.apple_oauth_client_id,
&cli.apple_oauth_client_secret,
)
.await?;
@ -382,7 +410,6 @@ async fn main() -> anyhow::Result<()> {
let state_crawler = state.clone();
let state_pb = state.clone();
let state_postcode_stats = state.clone();
let state_area_summary = state.clone();
let state_places = state.clone();
let state_shorten = state.clone();
let state_short_url = state.clone();
@ -447,7 +474,7 @@ async fn main() -> anyhow::Result<()> {
)
.route(
"/api/screenshot",
get(move |query| routes::get_screenshot(state_screenshot.clone(), query)),
get(move |headers, query| routes::get_screenshot(state_screenshot.clone(), headers, query)),
)
.route(
"/api/export",
@ -455,11 +482,6 @@ async fn main() -> anyhow::Result<()> {
.layer(ConcurrencyLimitLayer::new(3)),
)
.route("/api/me", get(routes::get_me))
.route(
"/api/area-summary",
post(move |body| routes::post_area_summary(state_area_summary.clone(), body))
.layer(ConcurrencyLimitLayer::new(5)),
)
.route(
"/api/shorten",
post(move |body| routes::post_shorten(state_shorten.clone(), body)),