feat: add neighborhood asset and scoring api
This commit is contained in:
@@ -57,6 +57,11 @@ GET /health
|
|||||||
GET /ready
|
GET /ready
|
||||||
GET /api/v1/areas/scores?month=2026-05
|
GET /api/v1/areas/scores?month=2026-05
|
||||||
GET /api/v1/market/overview?month=2026-05
|
GET /api/v1/market/overview?month=2026-05
|
||||||
|
GET /api/v1/neighborhoods
|
||||||
|
GET /api/v1/neighborhoods?area_id=zhangjiang
|
||||||
|
GET /api/v1/neighborhoods/{neighborhood_id}
|
||||||
|
GET /api/v1/neighborhoods/scores?month=2026-05
|
||||||
|
GET /api/v1/neighborhoods/scores?month=2026-05&area_id=zhangjiang
|
||||||
GET /api/v1/watchlist
|
GET /api/v1/watchlist
|
||||||
POST /api/v1/watchlist
|
POST /api/v1/watchlist
|
||||||
PATCH /api/v1/watchlist/{watchlist_item_id}
|
PATCH /api/v1/watchlist/{watchlist_item_id}
|
||||||
|
|||||||
157
apps/api/migrations/202606230001_neighborhood_scores.sql
Normal file
157
apps/api/migrations/202606230001_neighborhood_scores.sql
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS gold.neighborhood_scores (
|
||||||
|
neighborhood_id TEXT NOT NULL REFERENCES silver.neighborhoods(neighborhood_id),
|
||||||
|
area_id TEXT NOT NULL REFERENCES silver.areas(area_id),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
area_name TEXT NOT NULL,
|
||||||
|
district TEXT NOT NULL,
|
||||||
|
month TEXT NOT NULL CHECK (month ~ '^[0-9]{4}-[0-9]{2}$'),
|
||||||
|
investment_score DOUBLE PRECISION NOT NULL CHECK (investment_score BETWEEN 0 AND 100),
|
||||||
|
recommendation TEXT NOT NULL,
|
||||||
|
liquidity_score DOUBLE PRECISION NOT NULL CHECK (liquidity_score BETWEEN 0 AND 100),
|
||||||
|
rent_support_score DOUBLE PRECISION NOT NULL CHECK (rent_support_score BETWEEN 0 AND 100),
|
||||||
|
price_safety_score DOUBLE PRECISION NOT NULL CHECK (price_safety_score BETWEEN 0 AND 100),
|
||||||
|
location_score DOUBLE PRECISION NOT NULL CHECK (location_score BETWEEN 0 AND 100),
|
||||||
|
building_age_score DOUBLE PRECISION NOT NULL CHECK (building_age_score BETWEEN 0 AND 100),
|
||||||
|
transaction_count INTEGER NOT NULL CHECK (transaction_count >= 0),
|
||||||
|
transaction_price_psm DOUBLE PRECISION NOT NULL CHECK (transaction_price_psm >= 0),
|
||||||
|
listing_count INTEGER NOT NULL CHECK (listing_count >= 0),
|
||||||
|
listing_price_psm DOUBLE PRECISION NOT NULL CHECK (listing_price_psm >= 0),
|
||||||
|
rent_price_psm DOUBLE PRECISION NOT NULL CHECK (rent_price_psm >= 0),
|
||||||
|
median_days_on_market DOUBLE PRECISION NOT NULL CHECK (median_days_on_market >= 0),
|
||||||
|
annual_rent_yield_pct DOUBLE PRECISION NOT NULL CHECK (annual_rent_yield_pct >= 0),
|
||||||
|
discount_pct DOUBLE PRECISION NOT NULL,
|
||||||
|
available_units INTEGER NOT NULL CHECK (available_units >= 0),
|
||||||
|
model_version TEXT NOT NULL DEFAULT 'sample-v1',
|
||||||
|
computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (neighborhood_id, month)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gold_neighborhood_scores_month
|
||||||
|
ON gold.neighborhood_scores(month, investment_score DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gold_neighborhood_scores_area_month
|
||||||
|
ON gold.neighborhood_scores(area_id, month, investment_score DESC);
|
||||||
|
|
||||||
|
INSERT INTO silver.neighborhoods (
|
||||||
|
neighborhood_id,
|
||||||
|
area_id,
|
||||||
|
name,
|
||||||
|
built_year,
|
||||||
|
property_type,
|
||||||
|
metro_distance_m,
|
||||||
|
school_quality,
|
||||||
|
notes
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
('qiantan_a', 'qiantan', '前滩样例小区A', 2018, '商品住宅', 450, 'good', '演示样例'),
|
||||||
|
('qiantan_b', 'qiantan', '前滩样例小区B', 2021, '商品住宅', 650, 'normal', '演示样例'),
|
||||||
|
('xujiahui_a', 'xujiahui', '徐家汇样例小区A', 2008, '商品住宅', 300, 'excellent', '演示样例'),
|
||||||
|
('xujiahui_b', 'xujiahui', '徐家汇样例小区B', 1998, '商品住宅', 520, 'good', '演示样例'),
|
||||||
|
('danning_a', 'danning', '大宁样例小区A', 2015, '商品住宅', 600, 'good', '演示样例'),
|
||||||
|
('danning_b', 'danning', '大宁样例小区B', 2006, '商品住宅', 850, 'normal', '演示样例'),
|
||||||
|
('zhangjiang_a', 'zhangjiang', '张江样例小区A', 2016, '商品住宅', 700, 'normal', '演示样例'),
|
||||||
|
('zhangjiang_b', 'zhangjiang', '张江样例小区B', 2020, '商品住宅', 500, 'good', '演示样例'),
|
||||||
|
('hongqiao_a', 'hongqiao', '大虹桥样例小区A', 2020, '商品住宅', 900, 'normal', '演示样例'),
|
||||||
|
('hongqiao_b', 'hongqiao', '大虹桥样例小区B', 2012, '商品住宅', 1200, 'normal', '演示样例')
|
||||||
|
ON CONFLICT (neighborhood_id) DO UPDATE
|
||||||
|
SET area_id = EXCLUDED.area_id,
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
built_year = EXCLUDED.built_year,
|
||||||
|
property_type = EXCLUDED.property_type,
|
||||||
|
metro_distance_m = EXCLUDED.metro_distance_m,
|
||||||
|
school_quality = EXCLUDED.school_quality,
|
||||||
|
notes = EXCLUDED.notes,
|
||||||
|
updated_at = now();
|
||||||
|
|
||||||
|
INSERT INTO silver.neighborhood_monthly_metrics (
|
||||||
|
neighborhood_id,
|
||||||
|
month,
|
||||||
|
transaction_count,
|
||||||
|
transaction_price_psm,
|
||||||
|
listing_count,
|
||||||
|
listing_price_psm,
|
||||||
|
rent_price_psm,
|
||||||
|
median_days_on_market,
|
||||||
|
available_units,
|
||||||
|
source
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
('qiantan_a', '2026-05', 12, 124000, 38, 128000, 195, 50, 38, 'sample'),
|
||||||
|
('qiantan_b', '2026-05', 8, 119500, 42, 124000, 186, 57, 42, 'sample'),
|
||||||
|
('xujiahui_a', '2026-05', 8, 131000, 30, 137000, 215, 62, 30, 'sample'),
|
||||||
|
('xujiahui_b', '2026-05', 6, 119000, 36, 126500, 198, 78, 36, 'sample'),
|
||||||
|
('danning_a', '2026-05', 14, 100000, 45, 104000, 162, 65, 45, 'sample'),
|
||||||
|
('danning_b', '2026-05', 11, 93000, 52, 99000, 151, 74, 52, 'sample'),
|
||||||
|
('zhangjiang_a', '2026-05', 18, 86000, 58, 90500, 143, 58, 58, 'sample'),
|
||||||
|
('zhangjiang_b', '2026-05', 16, 89000, 46, 93000, 148, 52, 46, 'sample'),
|
||||||
|
('hongqiao_a', '2026-05', 9, 77000, 76, 83500, 121, 94, 76, 'sample'),
|
||||||
|
('hongqiao_b', '2026-05', 7, 72000, 88, 79500, 113, 102, 88, 'sample')
|
||||||
|
ON CONFLICT (neighborhood_id, month) DO UPDATE
|
||||||
|
SET transaction_count = EXCLUDED.transaction_count,
|
||||||
|
transaction_price_psm = EXCLUDED.transaction_price_psm,
|
||||||
|
listing_count = EXCLUDED.listing_count,
|
||||||
|
listing_price_psm = EXCLUDED.listing_price_psm,
|
||||||
|
rent_price_psm = EXCLUDED.rent_price_psm,
|
||||||
|
median_days_on_market = EXCLUDED.median_days_on_market,
|
||||||
|
available_units = EXCLUDED.available_units,
|
||||||
|
source = EXCLUDED.source,
|
||||||
|
updated_at = now();
|
||||||
|
|
||||||
|
INSERT INTO gold.neighborhood_scores (
|
||||||
|
neighborhood_id,
|
||||||
|
area_id,
|
||||||
|
name,
|
||||||
|
area_name,
|
||||||
|
district,
|
||||||
|
month,
|
||||||
|
investment_score,
|
||||||
|
recommendation,
|
||||||
|
liquidity_score,
|
||||||
|
rent_support_score,
|
||||||
|
price_safety_score,
|
||||||
|
location_score,
|
||||||
|
building_age_score,
|
||||||
|
transaction_count,
|
||||||
|
transaction_price_psm,
|
||||||
|
listing_count,
|
||||||
|
listing_price_psm,
|
||||||
|
rent_price_psm,
|
||||||
|
median_days_on_market,
|
||||||
|
annual_rent_yield_pct,
|
||||||
|
discount_pct,
|
||||||
|
available_units,
|
||||||
|
model_version
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
('zhangjiang_b', 'zhangjiang', '张江样例小区B', '张江', '浦东新区', '2026-05', 76.8, '重点研究', 84.0, 72.0, 58.0, 78.0, 88.0, 16, 89000, 46, 93000, 148, 52, 2.00, 4.30, 46, 'sample-v1'),
|
||||||
|
('zhangjiang_a', 'zhangjiang', '张江样例小区A', '张江', '浦东新区', '2026-05', 73.4, '观察池', 88.0, 70.0, 54.0, 68.0, 76.0, 18, 86000, 58, 90500, 143, 58, 1.99, 4.97, 58, 'sample-v1'),
|
||||||
|
('qiantan_a', 'qiantan', '前滩样例小区A', '前滩', '浦东新区', '2026-05', 71.6, '观察池', 75.0, 63.0, 48.0, 82.0, 82.0, 12, 124000, 38, 128000, 195, 50, 1.89, 3.13, 38, 'sample-v1'),
|
||||||
|
('danning_a', 'danning', '大宁样例小区A', '大宁', '静安区', '2026-05', 69.2, '观察池', 78.0, 66.0, 52.0, 70.0, 78.0, 14, 100000, 45, 104000, 162, 65, 1.94, 3.85, 45, 'sample-v1'),
|
||||||
|
('xujiahui_a', 'xujiahui', '徐家汇样例小区A', '徐家汇', '徐汇区', '2026-05', 66.9, '观察池', 63.0, 68.0, 45.0, 92.0, 58.0, 8, 131000, 30, 137000, 215, 62, 1.97, 4.38, 30, 'sample-v1'),
|
||||||
|
('qiantan_b', 'qiantan', '前滩样例小区B', '前滩', '浦东新区', '2026-05', 65.4, '观察池', 62.0, 61.0, 55.0, 72.0, 94.0, 8, 119500, 42, 124000, 186, 57, 1.87, 3.63, 42, 'sample-v1'),
|
||||||
|
('danning_b', 'danning', '大宁样例小区B', '大宁', '静安区', '2026-05', 62.5, '中性观望', 60.0, 64.0, 62.0, 58.0, 54.0, 11, 93000, 52, 99000, 151, 74, 1.95, 6.06, 52, 'sample-v1'),
|
||||||
|
('xujiahui_b', 'xujiahui', '徐家汇样例小区B', '徐家汇', '徐汇区', '2026-05', 59.8, '中性观望', 48.0, 66.0, 64.0, 76.0, 34.0, 6, 119000, 36, 126500, 198, 78, 2.00, 5.93, 36, 'sample-v1'),
|
||||||
|
('hongqiao_a', 'hongqiao', '大虹桥样例小区A', '大虹桥', '闵行区', '2026-05', 54.7, '谨慎等待', 42.0, 61.0, 70.0, 48.0, 90.0, 9, 77000, 76, 83500, 121, 94, 1.89, 7.78, 76, 'sample-v1'),
|
||||||
|
('hongqiao_b', 'hongqiao', '大虹桥样例小区B', '大虹桥', '闵行区', '2026-05', 49.6, '谨慎等待', 34.0, 58.0, 76.0, 36.0, 64.0, 7, 72000, 88, 79500, 113, 102, 1.88, 9.43, 88, 'sample-v1')
|
||||||
|
ON CONFLICT (neighborhood_id, month) DO UPDATE
|
||||||
|
SET area_id = EXCLUDED.area_id,
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
area_name = EXCLUDED.area_name,
|
||||||
|
district = EXCLUDED.district,
|
||||||
|
investment_score = EXCLUDED.investment_score,
|
||||||
|
recommendation = EXCLUDED.recommendation,
|
||||||
|
liquidity_score = EXCLUDED.liquidity_score,
|
||||||
|
rent_support_score = EXCLUDED.rent_support_score,
|
||||||
|
price_safety_score = EXCLUDED.price_safety_score,
|
||||||
|
location_score = EXCLUDED.location_score,
|
||||||
|
building_age_score = EXCLUDED.building_age_score,
|
||||||
|
transaction_count = EXCLUDED.transaction_count,
|
||||||
|
transaction_price_psm = EXCLUDED.transaction_price_psm,
|
||||||
|
listing_count = EXCLUDED.listing_count,
|
||||||
|
listing_price_psm = EXCLUDED.listing_price_psm,
|
||||||
|
rent_price_psm = EXCLUDED.rent_price_psm,
|
||||||
|
median_days_on_market = EXCLUDED.median_days_on_market,
|
||||||
|
annual_rent_yield_pct = EXCLUDED.annual_rent_yield_pct,
|
||||||
|
discount_pct = EXCLUDED.discount_pct,
|
||||||
|
available_units = EXCLUDED.available_units,
|
||||||
|
model_version = EXCLUDED.model_version,
|
||||||
|
computed_at = now();
|
||||||
@@ -4,8 +4,9 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::error::{ApiError, ApiResult};
|
use crate::error::{ApiError, ApiResult};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
AreaScore, AreaScoreSummary, CreateWatchlistItem, MarketOverview, MonthQuery,
|
AreaScore, AreaScoreSummary, CreateWatchlistItem, MarketOverview, MonthQuery, Neighborhood,
|
||||||
UpdateWatchlistItem, WatchlistItem,
|
NeighborhoodQuery, NeighborhoodScore, NeighborhoodScoreQuery, UpdateWatchlistItem,
|
||||||
|
WatchlistItem,
|
||||||
};
|
};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
@@ -70,6 +71,111 @@ pub async fn market_overview(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list_neighborhoods(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Query(query): Query<NeighborhoodQuery>,
|
||||||
|
) -> ApiResult<Json<Vec<Neighborhood>>> {
|
||||||
|
let neighborhoods = sqlx::query_as::<_, Neighborhood>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
n.neighborhood_id,
|
||||||
|
n.area_id,
|
||||||
|
a.name AS area_name,
|
||||||
|
a.district,
|
||||||
|
n.name,
|
||||||
|
n.built_year,
|
||||||
|
n.property_type,
|
||||||
|
n.metro_distance_m,
|
||||||
|
n.school_quality,
|
||||||
|
n.notes
|
||||||
|
FROM silver.neighborhoods n
|
||||||
|
JOIN silver.areas a ON a.area_id = n.area_id
|
||||||
|
WHERE ($1::text IS NULL OR n.area_id = $1)
|
||||||
|
ORDER BY a.district, a.name, n.name
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(query.area_id)
|
||||||
|
.fetch_all(&state.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(neighborhoods))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_neighborhood(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(neighborhood_id): Path<String>,
|
||||||
|
) -> ApiResult<Json<Neighborhood>> {
|
||||||
|
let neighborhood = sqlx::query_as::<_, Neighborhood>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
n.neighborhood_id,
|
||||||
|
n.area_id,
|
||||||
|
a.name AS area_name,
|
||||||
|
a.district,
|
||||||
|
n.name,
|
||||||
|
n.built_year,
|
||||||
|
n.property_type,
|
||||||
|
n.metro_distance_m,
|
||||||
|
n.school_quality,
|
||||||
|
n.notes
|
||||||
|
FROM silver.neighborhoods n
|
||||||
|
JOIN silver.areas a ON a.area_id = n.area_id
|
||||||
|
WHERE n.neighborhood_id = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(neighborhood_id)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| ApiError::BadRequest("neighborhood not found".to_string()))?;
|
||||||
|
|
||||||
|
Ok(Json(neighborhood))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_neighborhood_scores(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Query(query): Query<NeighborhoodScoreQuery>,
|
||||||
|
) -> ApiResult<Json<Vec<NeighborhoodScore>>> {
|
||||||
|
validate_month(&query.month)?;
|
||||||
|
|
||||||
|
let scores = sqlx::query_as::<_, NeighborhoodScore>(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
neighborhood_id,
|
||||||
|
area_id,
|
||||||
|
name,
|
||||||
|
area_name,
|
||||||
|
district,
|
||||||
|
month,
|
||||||
|
investment_score,
|
||||||
|
recommendation,
|
||||||
|
liquidity_score,
|
||||||
|
rent_support_score,
|
||||||
|
price_safety_score,
|
||||||
|
location_score,
|
||||||
|
building_age_score,
|
||||||
|
transaction_count,
|
||||||
|
transaction_price_psm,
|
||||||
|
listing_count,
|
||||||
|
listing_price_psm,
|
||||||
|
rent_price_psm,
|
||||||
|
median_days_on_market,
|
||||||
|
annual_rent_yield_pct,
|
||||||
|
discount_pct,
|
||||||
|
available_units
|
||||||
|
FROM gold.neighborhood_scores
|
||||||
|
WHERE month = $1
|
||||||
|
AND ($2::text IS NULL OR area_id = $2)
|
||||||
|
ORDER BY investment_score DESC, neighborhood_id ASC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(query.month)
|
||||||
|
.bind(query.area_id)
|
||||||
|
.fetch_all(&state.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(scores))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_watchlist_items(
|
pub async fn list_watchlist_items(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> ApiResult<Json<Vec<WatchlistItem>>> {
|
) -> ApiResult<Json<Vec<WatchlistItem>>> {
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ pub struct MonthQuery {
|
|||||||
pub month: String,
|
pub month: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct NeighborhoodQuery {
|
||||||
|
pub area_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct NeighborhoodScoreQuery {
|
||||||
|
pub month: String,
|
||||||
|
pub area_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, FromRow)]
|
#[derive(Debug, Clone, Serialize, FromRow)]
|
||||||
pub struct AreaScore {
|
pub struct AreaScore {
|
||||||
pub area_id: String,
|
pub area_id: String,
|
||||||
@@ -67,6 +78,46 @@ pub struct MarketOverview {
|
|||||||
pub highest_supply_risk_area: Option<AreaScoreSummary>,
|
pub highest_supply_risk_area: Option<AreaScoreSummary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, FromRow)]
|
||||||
|
pub struct Neighborhood {
|
||||||
|
pub neighborhood_id: String,
|
||||||
|
pub area_id: String,
|
||||||
|
pub area_name: String,
|
||||||
|
pub district: String,
|
||||||
|
pub name: String,
|
||||||
|
pub built_year: Option<i32>,
|
||||||
|
pub property_type: String,
|
||||||
|
pub metro_distance_m: Option<i32>,
|
||||||
|
pub school_quality: Option<String>,
|
||||||
|
pub notes: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, FromRow)]
|
||||||
|
pub struct NeighborhoodScore {
|
||||||
|
pub neighborhood_id: String,
|
||||||
|
pub area_id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub area_name: String,
|
||||||
|
pub district: String,
|
||||||
|
pub month: String,
|
||||||
|
pub investment_score: f64,
|
||||||
|
pub recommendation: String,
|
||||||
|
pub liquidity_score: f64,
|
||||||
|
pub rent_support_score: f64,
|
||||||
|
pub price_safety_score: f64,
|
||||||
|
pub location_score: f64,
|
||||||
|
pub building_age_score: f64,
|
||||||
|
pub transaction_count: i32,
|
||||||
|
pub transaction_price_psm: f64,
|
||||||
|
pub listing_count: i32,
|
||||||
|
pub listing_price_psm: f64,
|
||||||
|
pub rent_price_psm: f64,
|
||||||
|
pub median_days_on_market: f64,
|
||||||
|
pub annual_rent_yield_pct: f64,
|
||||||
|
pub discount_pct: f64,
|
||||||
|
pub available_units: i32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, FromRow)]
|
#[derive(Debug, Clone, Serialize, FromRow)]
|
||||||
pub struct WatchlistItem {
|
pub struct WatchlistItem {
|
||||||
pub watchlist_item_id: i64,
|
pub watchlist_item_id: i64,
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ use tower_http::cors::CorsLayer;
|
|||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
use crate::handlers::{
|
use crate::handlers::{
|
||||||
archive_watchlist_item, create_watchlist_item, health, list_area_scores, list_watchlist_items,
|
archive_watchlist_item, create_watchlist_item, get_neighborhood, health, list_area_scores,
|
||||||
market_overview, ready, update_watchlist_item,
|
list_neighborhood_scores, list_neighborhoods, list_watchlist_items, market_overview, ready,
|
||||||
|
update_watchlist_item,
|
||||||
};
|
};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
@@ -18,6 +19,9 @@ pub fn build_router(state: AppState) -> Router {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/areas/scores", get(list_area_scores))
|
.route("/areas/scores", get(list_area_scores))
|
||||||
.route("/market/overview", get(market_overview))
|
.route("/market/overview", get(market_overview))
|
||||||
|
.route("/neighborhoods", get(list_neighborhoods))
|
||||||
|
.route("/neighborhoods/scores", get(list_neighborhood_scores))
|
||||||
|
.route("/neighborhoods/{neighborhood_id}", get(get_neighborhood))
|
||||||
.route(
|
.route(
|
||||||
"/watchlist",
|
"/watchlist",
|
||||||
get(list_watchlist_items).post(create_watchlist_item),
|
get(list_watchlist_items).post(create_watchlist_item),
|
||||||
|
|||||||
Reference in New Issue
Block a user