@
将 web/routes.py 拆分为模块化 router + service 架构
- 新增 web/app_factory.py 集中注册 7 个域名 router
- 新增 web/routers/ 薄壳路由层(auth/city/system/scan/ops/payments/analytics)
- 新增 web/services/ 业务函数下沉(每域独立 service 文件)
- web/routes.py 缩减为 city_runtime 的兼容重导出 facade
- analysis_service.py/app.py 适配新入口并清理冗余导入
Scope-risk: LOW — 全量 170 测试通过,router 注册顺序与原路由一致
Tested: python -m pytest -q (170 passed), ruff check . (All checks passed)
@
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Router package for grouped FastAPI route modules."""
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Analytics event API routes."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from web.core import AnalyticsEventRequest
|
||||
from web.services.analytics_api import track_analytics_event
|
||||
|
||||
router = APIRouter(tags=["analytics"])
|
||||
|
||||
|
||||
@router.post("/api/analytics/events")
|
||||
async def analytics_track(request: Request, body: AnalyticsEventRequest):
|
||||
return track_analytics_event(request, body)
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Authentication API routes."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from web.services.auth_api import get_auth_me_payload
|
||||
|
||||
router = APIRouter(tags=["auth"])
|
||||
|
||||
|
||||
@router.get("/api/auth/me")
|
||||
async def auth_me(request: Request):
|
||||
return get_auth_me_payload(request)
|
||||
@@ -0,0 +1,103 @@
|
||||
"""City and city-analysis API routes."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Request
|
||||
|
||||
from web.services.city_api import (
|
||||
get_city_detail_aggregate_payload,
|
||||
get_city_detail_payload,
|
||||
get_city_history_payload,
|
||||
get_city_market_scan_payload,
|
||||
get_city_summary_payload,
|
||||
list_cities_payload,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["city"])
|
||||
|
||||
|
||||
@router.get("/api/cities")
|
||||
async def list_cities(request: Request):
|
||||
return await list_cities_payload(request)
|
||||
|
||||
|
||||
@router.get("/api/city/{name}")
|
||||
async def city_detail(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
name: str,
|
||||
force_refresh: bool = False,
|
||||
depth: str = "panel",
|
||||
):
|
||||
return await get_city_detail_payload(
|
||||
request,
|
||||
name,
|
||||
force_refresh=force_refresh,
|
||||
depth=depth,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/history/{name}")
|
||||
async def city_history(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
name: str,
|
||||
include_records: bool = False,
|
||||
):
|
||||
return await get_city_history_payload(
|
||||
request,
|
||||
name,
|
||||
include_records=include_records,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/city/{name}/summary")
|
||||
async def city_summary(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
name: str,
|
||||
force_refresh: bool = False,
|
||||
):
|
||||
return await get_city_summary_payload(
|
||||
request,
|
||||
name,
|
||||
force_refresh=force_refresh,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/city/{name}/detail")
|
||||
async def city_detail_aggregate(
|
||||
request: Request,
|
||||
name: str,
|
||||
force_refresh: bool = False,
|
||||
market_slug: Optional[str] = None,
|
||||
target_date: Optional[str] = None,
|
||||
):
|
||||
return await get_city_detail_aggregate_payload(
|
||||
request,
|
||||
name,
|
||||
force_refresh=force_refresh,
|
||||
market_slug=market_slug,
|
||||
target_date=target_date,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/city/{name}/market-scan")
|
||||
async def city_market_scan(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
name: str,
|
||||
force_refresh: bool = False,
|
||||
market_slug: Optional[str] = None,
|
||||
target_date: Optional[str] = None,
|
||||
lite: bool = False,
|
||||
):
|
||||
return await get_city_market_scan_payload(
|
||||
request,
|
||||
background_tasks,
|
||||
name,
|
||||
force_refresh=force_refresh,
|
||||
market_slug=market_slug,
|
||||
target_date=target_date,
|
||||
lite=lite,
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Operations/admin API routes."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from web.core import GrantPointsRequest
|
||||
from web.services.ops_api import (
|
||||
get_ops_analytics_funnel,
|
||||
get_ops_truth_history,
|
||||
get_ops_weekly_leaderboard,
|
||||
grant_ops_points,
|
||||
list_ops_memberships,
|
||||
list_ops_payment_incidents,
|
||||
resolve_ops_payment_incident,
|
||||
search_ops_users,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["ops"])
|
||||
|
||||
|
||||
@router.get("/api/ops/users")
|
||||
async def ops_search_users(request: Request, q: str = "", limit: int = 20):
|
||||
return search_ops_users(request, q=q, limit=limit)
|
||||
|
||||
|
||||
@router.get("/api/ops/leaderboard/weekly")
|
||||
async def ops_weekly_leaderboard(request: Request, limit: int = 20):
|
||||
return get_ops_weekly_leaderboard(request, limit=limit)
|
||||
|
||||
|
||||
@router.get("/api/ops/memberships")
|
||||
async def ops_memberships(request: Request, limit: int = 200):
|
||||
return list_ops_memberships(request, limit=limit)
|
||||
|
||||
|
||||
@router.get("/api/ops/payments/incidents")
|
||||
async def ops_payment_incidents(
|
||||
request: Request,
|
||||
limit: int = 50,
|
||||
reason: str = "",
|
||||
include_resolved: bool = False,
|
||||
):
|
||||
return list_ops_payment_incidents(
|
||||
request,
|
||||
limit=limit,
|
||||
reason=reason,
|
||||
include_resolved=include_resolved,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/ops/payments/incidents/{event_id}/resolve")
|
||||
async def ops_resolve_payment_incident(request: Request, event_id: int):
|
||||
return resolve_ops_payment_incident(request, event_id)
|
||||
|
||||
|
||||
@router.post("/api/ops/users/grant-points")
|
||||
async def ops_grant_points(request: Request, body: GrantPointsRequest):
|
||||
return grant_ops_points(request, body)
|
||||
|
||||
|
||||
@router.get("/api/ops/analytics/funnel")
|
||||
async def ops_analytics_funnel(request: Request, days: int = 30):
|
||||
return get_ops_analytics_funnel(request, days=days)
|
||||
|
||||
|
||||
@router.get("/api/ops/truth-history")
|
||||
async def ops_truth_history(
|
||||
request: Request,
|
||||
city: str = "",
|
||||
date_from: str = "",
|
||||
date_to: str = "",
|
||||
limit: int = 200,
|
||||
):
|
||||
return get_ops_truth_history(
|
||||
request,
|
||||
city=city,
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
limit=limit,
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Payment API routes."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from web.core import (
|
||||
ConfirmPaymentTxRequest,
|
||||
CreatePaymentIntentRequest,
|
||||
SubmitPaymentTxRequest,
|
||||
WalletChallengeRequest,
|
||||
WalletUnbindRequest,
|
||||
WalletVerifyRequest,
|
||||
)
|
||||
from web.services.payment_api import (
|
||||
confirm_payment_tx as confirm_payment_tx_service,
|
||||
create_payment_intent as create_payment_intent_service,
|
||||
create_payment_wallet_challenge,
|
||||
get_payment_config,
|
||||
get_payment_intent,
|
||||
get_payment_runtime,
|
||||
list_payment_wallets,
|
||||
reconcile_latest_payment,
|
||||
submit_payment_tx as submit_payment_tx_service,
|
||||
unbind_payment_wallet,
|
||||
verify_payment_wallet,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["payments"])
|
||||
|
||||
|
||||
@router.get("/api/payments/config")
|
||||
async def payment_config(request: Request):
|
||||
return get_payment_config(request)
|
||||
|
||||
|
||||
@router.get("/api/payments/runtime")
|
||||
async def payment_runtime(request: Request):
|
||||
return get_payment_runtime(request)
|
||||
|
||||
|
||||
@router.get("/api/payments/wallets")
|
||||
async def payment_wallets(request: Request):
|
||||
return list_payment_wallets(request)
|
||||
|
||||
|
||||
@router.delete("/api/payments/wallets")
|
||||
async def payment_wallet_unbind(request: Request, body: WalletUnbindRequest):
|
||||
return unbind_payment_wallet(request, body)
|
||||
|
||||
|
||||
@router.post("/api/payments/wallets/challenge")
|
||||
async def payment_wallet_challenge(request: Request, body: WalletChallengeRequest):
|
||||
return create_payment_wallet_challenge(request, body)
|
||||
|
||||
|
||||
@router.post("/api/payments/wallets/verify")
|
||||
async def payment_wallet_verify(request: Request, body: WalletVerifyRequest):
|
||||
return verify_payment_wallet(request, body)
|
||||
|
||||
|
||||
@router.post("/api/payments/intents")
|
||||
async def payment_create_intent(request: Request, body: CreatePaymentIntentRequest):
|
||||
return create_payment_intent_service(request, body)
|
||||
|
||||
|
||||
@router.get("/api/payments/intents/{intent_id}")
|
||||
async def payment_get_intent(request: Request, intent_id: str):
|
||||
return get_payment_intent(request, intent_id)
|
||||
|
||||
|
||||
@router.post("/api/payments/intents/{intent_id}/submit")
|
||||
async def payment_submit_tx(
|
||||
request: Request,
|
||||
intent_id: str,
|
||||
body: SubmitPaymentTxRequest,
|
||||
):
|
||||
return submit_payment_tx_service(request, intent_id, body)
|
||||
|
||||
|
||||
@router.post("/api/payments/intents/{intent_id}/confirm")
|
||||
async def payment_confirm_tx(
|
||||
request: Request,
|
||||
intent_id: str,
|
||||
body: ConfirmPaymentTxRequest,
|
||||
):
|
||||
return confirm_payment_tx_service(request, intent_id, body)
|
||||
|
||||
|
||||
@router.post("/api/payments/reconcile-latest")
|
||||
async def payment_reconcile_latest(request: Request):
|
||||
return reconcile_latest_payment(request)
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Market scan and scan AI API routes."""
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from web.services.scan_api import (
|
||||
get_scan_city_ai_forecast_payload,
|
||||
get_scan_city_ai_stream_response,
|
||||
get_scan_terminal_ai_payload,
|
||||
get_scan_terminal_payload,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["scan"])
|
||||
|
||||
|
||||
@router.get("/api/scan/terminal")
|
||||
async def scan_terminal(
|
||||
request: Request,
|
||||
scan_mode: str = "tradable",
|
||||
min_price: float = 0.05,
|
||||
max_price: float = 0.95,
|
||||
min_edge_pct: float = 2.0,
|
||||
min_liquidity: float = 500.0,
|
||||
high_liquidity_only: bool = False,
|
||||
market_type: str = "maxtemp",
|
||||
time_range: str = "today",
|
||||
limit: int = 25,
|
||||
force_refresh: bool = False,
|
||||
):
|
||||
return await get_scan_terminal_payload(
|
||||
request,
|
||||
scan_mode=scan_mode,
|
||||
min_price=min_price,
|
||||
max_price=max_price,
|
||||
min_edge_pct=min_edge_pct,
|
||||
min_liquidity=min_liquidity,
|
||||
high_liquidity_only=high_liquidity_only,
|
||||
market_type=market_type,
|
||||
time_range=time_range,
|
||||
limit=limit,
|
||||
force_refresh=force_refresh,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/scan/terminal/ai")
|
||||
async def scan_terminal_ai(request: Request):
|
||||
return await get_scan_terminal_ai_payload(request)
|
||||
|
||||
|
||||
@router.post("/api/scan/terminal/ai-city")
|
||||
async def scan_terminal_ai_city(request: Request):
|
||||
return await get_scan_city_ai_forecast_payload(request)
|
||||
|
||||
|
||||
@router.post("/api/scan/terminal/ai-city/stream")
|
||||
async def scan_terminal_ai_city_stream(request: Request):
|
||||
return await get_scan_city_ai_stream_response(request)
|
||||
@@ -0,0 +1,63 @@
|
||||
"""System and observability API routes for PolyWeather."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Request
|
||||
from fastapi.responses import PlainTextResponse
|
||||
|
||||
from web.services.system_api import (
|
||||
get_health_payload,
|
||||
get_prometheus_metrics_response,
|
||||
get_system_cache_status,
|
||||
get_system_status_payload,
|
||||
run_system_prewarm,
|
||||
run_system_priority_warm,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["system"])
|
||||
|
||||
|
||||
@router.get("/healthz")
|
||||
async def healthz():
|
||||
return get_health_payload()
|
||||
|
||||
|
||||
@router.get("/api/system/status")
|
||||
async def system_status():
|
||||
return await get_system_status_payload()
|
||||
|
||||
|
||||
@router.post("/api/system/prewarm")
|
||||
async def system_prewarm(
|
||||
request: Request,
|
||||
cities: Optional[str] = None,
|
||||
force_refresh: bool = False,
|
||||
include_detail: bool = False,
|
||||
include_market: bool = False,
|
||||
):
|
||||
return run_system_prewarm(
|
||||
request,
|
||||
cities=cities,
|
||||
force_refresh=force_refresh,
|
||||
include_detail=include_detail,
|
||||
include_market=include_market,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/system/cache-status")
|
||||
async def system_cache_status(request: Request, cities: Optional[str] = None):
|
||||
return get_system_cache_status(request, cities=cities)
|
||||
|
||||
|
||||
@router.post("/api/system/priority-warm")
|
||||
async def system_priority_warm(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
timezone: Optional[str] = None,
|
||||
):
|
||||
return run_system_priority_warm(request, background_tasks, timezone=timezone)
|
||||
|
||||
|
||||
@router.get("/metrics", response_class=PlainTextResponse)
|
||||
async def metrics():
|
||||
return get_prometheus_metrics_response()
|
||||
Reference in New Issue
Block a user