合并yy并同步远程qyqy_develop
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Header
|
||||
from fastapi import APIRouter, Depends, Header, Query
|
||||
from pydantic import Field
|
||||
|
||||
from app.api.dependencies.auth import build_request_context
|
||||
@@ -9,6 +9,7 @@ from app.api.schemas.admin import StrictPayload
|
||||
from app.api.schemas.conversations import HandoverRequest
|
||||
from app.core.contracts import RequestContext
|
||||
from app.service.public_platform_service import PublicPlatformService
|
||||
from app.service.public_product_service import PublicProductService
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["public-platform"],
|
||||
dependencies=[Depends(enforce_rate_limit)])
|
||||
@@ -118,3 +119,34 @@ async def decide_memory_candidate(
|
||||
return await CustomerProfileCandidateService().decide_by_customer(
|
||||
candidate_id, payload.decision, context
|
||||
)
|
||||
|
||||
|
||||
@router.get("/products")
|
||||
async def list_products(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
) -> dict[str, Any]:
|
||||
"""公开产品列表:在售场内基金 + 各自最新行情(访客令牌即可访问)。
|
||||
|
||||
这是访客三个页面(首页推荐 / 产品列表 / 产品详情)的数据源,
|
||||
替代原先前端手写的 `common/mock-data.js`。
|
||||
|
||||
只要求**有效令牌**、不检查权限码:访客令牌的上下文只有 `roles=("visitor",)`
|
||||
且不带权限,与 `/api/v1/conversations`、`/api/v1/agent-runs` 的访客口径一致。
|
||||
"""
|
||||
return await PublicProductService().list_products(context)
|
||||
|
||||
|
||||
@router.get("/products/{product_code}/nav-history")
|
||||
async def product_nav_history(
|
||||
product_code: str,
|
||||
days: int = Query(default=90, ge=1, le=365),
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
) -> dict[str, Any]:
|
||||
"""产品历史净值序列(编号 `P002`):产品详情页净值走势图的数据源。
|
||||
|
||||
数据来自 `fin_nav_history`,由 `tools/sync_nav_history.py` 从东财净值接口同步。
|
||||
鉴权口径与 P001 相同:**要求有效令牌但不校验权限码**(访客令牌可用)。
|
||||
|
||||
表为空时返回 `count=0` 与空数组,**不是错误** —— 前端据此显示"尚未接入"。
|
||||
"""
|
||||
return await PublicProductService().nav_history(product_code, context, days=days)
|
||||
|
||||
@@ -41,6 +41,22 @@ async def published_recommendations(
|
||||
return await ProductRecommendationService().published(context)
|
||||
|
||||
|
||||
@admin_router.get(
|
||||
"/advisor/pending-contents",
|
||||
dependencies=[Depends(enforce_advisor_rollout)],
|
||||
)
|
||||
async def pending_advisor_contents(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
"""待审核的投顾内容(推荐方案 + 投资方案书)。编号 `A047`。
|
||||
|
||||
补这个入口的原因:审核/发布端点都要求先拿到 `content_id`,而此前**没有**任何
|
||||
端点能列出待审内容,管理员拿不到 id ⇒ 审核链路不可达。返回体里的
|
||||
`content_type` 用于前端区分两类内容。
|
||||
"""
|
||||
return await ProductRecommendationService().pending_reviews(context)
|
||||
|
||||
|
||||
@admin_router.post(
|
||||
"/advisor/recommendations/{content_id}/reviews",
|
||||
dependencies=[Depends(enforce_advisor_rollout)],
|
||||
|
||||
@@ -65,6 +65,16 @@ async def _idempotent_write(
|
||||
return await ApiTransactionService().execute_in(session, context, scope, key, body, action)
|
||||
|
||||
|
||||
def _risk_list_envelope(page: dict[str, Any], context: RequestContext) -> dict[str, object]:
|
||||
"""在统一列表信封上补充风控列表总数和页大小。"""
|
||||
response = _list_envelope(page, context)
|
||||
meta = response["meta"]
|
||||
if isinstance(meta, dict):
|
||||
meta["total"] = int(page.get("total") or 0)
|
||||
meta["page_size"] = int(page.get("page_size") or 0)
|
||||
return response
|
||||
|
||||
|
||||
@router.get("/overview")
|
||||
async def risk_overview(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
@@ -81,7 +91,7 @@ async def list_risk_alerts(
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
data = await RiskQueryService(session).list_alerts(context, query)
|
||||
return _list_envelope(data, context)
|
||||
return _risk_list_envelope(data, context)
|
||||
|
||||
|
||||
@router.post("/alerts/scan")
|
||||
@@ -231,7 +241,7 @@ async def list_risk_evidence(
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
data = await RiskQueryService(session).list_evidence(context, source, query)
|
||||
return _list_envelope(data, context)
|
||||
return _risk_list_envelope(data, context)
|
||||
|
||||
|
||||
@router.get("/notifications")
|
||||
@@ -241,7 +251,7 @@ async def list_risk_notifications(
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
data = await RiskNotificationService(session).list_notifications(context, query)
|
||||
return _list_envelope(data, context)
|
||||
return _risk_list_envelope(data, context)
|
||||
|
||||
|
||||
@router.post("/daily-report")
|
||||
|
||||
@@ -30,13 +30,20 @@ from app.api.dependencies.database import get_session
|
||||
from app.api.schemas.trading import OrderCreateRequest
|
||||
from app.api.views.envelope import envelope, list_envelope
|
||||
from app.core.contracts import RequestContext
|
||||
from app.service.authorization_service import AuthorizationService
|
||||
from app.service.suitability_service import SuitabilityService
|
||||
from app.service.trade_service import TradeService
|
||||
|
||||
router = APIRouter(prefix="/api/v1/users/me", tags=["trading"])
|
||||
|
||||
|
||||
def _service(session: AsyncSession, context: RequestContext) -> TradeService:
|
||||
return TradeService(session)
|
||||
return TradeService(session, suitability_evaluator=SuitabilityService())
|
||||
|
||||
|
||||
async def _authorize(context: RequestContext, permission: str) -> None:
|
||||
"""Enforce the endpoint permission declared in docs/05 before DB work."""
|
||||
await AuthorizationService.require(context, permission)
|
||||
|
||||
|
||||
# T001 账户看板
|
||||
@@ -45,6 +52,7 @@ async def get_account_dashboard(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "account:read:self")
|
||||
data = await _service(session, context).get_account_dashboard(context)
|
||||
return envelope(data, context)
|
||||
|
||||
@@ -56,6 +64,7 @@ async def submit_order(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "trade:order:create")
|
||||
data = await _service(session, context).submit_order(payload, context)
|
||||
return envelope(data, context)
|
||||
|
||||
@@ -68,6 +77,7 @@ async def list_orders(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "trade:order:read")
|
||||
cursor_id = int(cursor) if cursor else None
|
||||
items, next_cursor = await _service(session, context).list_orders(
|
||||
context, limit=limit, cursor=cursor_id
|
||||
@@ -85,6 +95,7 @@ async def get_order(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "trade:order:read")
|
||||
data = await _service(session, context).get_order(order_no, context)
|
||||
return envelope(data, context)
|
||||
|
||||
@@ -96,6 +107,7 @@ async def cancel_order(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "trade:order:cancel")
|
||||
order = await _service(session, context).cancel_order(order_no, context)
|
||||
return envelope(order, context)
|
||||
|
||||
@@ -106,6 +118,7 @@ async def list_holdings(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "holding:read:self")
|
||||
data = await _service(session, context).list_holdings(context)
|
||||
return envelope(data, context)
|
||||
|
||||
@@ -118,6 +131,7 @@ async def list_transactions(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "trade:txn:read")
|
||||
cursor_id = int(cursor) if cursor else None
|
||||
data = await _service(session, context).list_transactions(
|
||||
context, limit=limit, cursor=cursor_id
|
||||
@@ -132,6 +146,7 @@ async def get_transaction(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "trade:txn:read")
|
||||
item = await _service(session, context).get_transaction(txn_no, context)
|
||||
return envelope(item, context)
|
||||
|
||||
@@ -144,8 +159,9 @@ async def list_cash_ledger(
|
||||
context: RequestContext = Depends(build_request_context), # noqa: B008
|
||||
session: AsyncSession = Depends(get_session), # noqa: B008
|
||||
) -> dict[str, object]:
|
||||
await _authorize(context, "account:read:self")
|
||||
cursor_id = int(cursor) if cursor else None
|
||||
data = await _service(session, context).list_cash_ledger(
|
||||
context, limit=limit, cursor=cursor_id
|
||||
)
|
||||
return envelope(data, context)
|
||||
return envelope(data, context)
|
||||
|
||||
Reference in New Issue
Block a user