This commit is contained in:
aaron 2026-05-17 00:15:10 +08:00
parent ce3084056c
commit 94b1cffa40
4 changed files with 70 additions and 4 deletions

View File

@ -94,6 +94,10 @@ def build_router(templates, repo_root: Path, stock_report_template: str):
user, redirect = require_page_user(request)
if redirect:
return redirect
try:
require_admin(request.cookies.get("altcoin_session", ""))
except HTTPException as exc:
return HTMLResponse(content=f"<meta charset=utf-8><h2>需要管理员权限</h2><p>{exc.detail}</p><a href=/app>返回看板</a>", status_code=exc.status_code)
return render_page("paper_trading.html", request, active_nav="paper_trading")
@router.get("/strategy", response_class=HTMLResponse)

View File

@ -1,7 +1,7 @@
from fastapi import APIRouter, Cookie
from app.db.paper_trading import get_paper_trading_summary, list_paper_trades
from app.web.shared import require_api_user_with_subscription
from app.web.shared import require_admin
router = APIRouter()
@ -9,7 +9,7 @@ router = APIRouter()
@router.get("/api/paper-trading/summary")
async def api_paper_trading_summary(days: int = 30, altcoin_session: str = Cookie(default="")):
require_api_user_with_subscription(altcoin_session)
require_admin(altcoin_session)
return get_paper_trading_summary(days=days)
@ -20,5 +20,5 @@ async def api_paper_trading_trades(
status: str = "",
altcoin_session: str = Cookie(default=""),
):
require_api_user_with_subscription(altcoin_session)
require_admin(altcoin_session)
return list_paper_trades(limit=limit, offset=offset, status=status)

View File

@ -175,7 +175,7 @@ a { color: inherit; text-decoration: none; }
<nav class="sidebar-nav">
<div class="sidebar-section-label">交易</div>
<a class="sidebar-link {% if active_nav | default('app') == 'app' %}active{% endif %}" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
<a class="sidebar-link {% if active_nav == 'paper_trading' %}active{% endif %}" href="/paper-trading"><svg class="link-icon"><use href="#svg-paper"/></svg>模拟交易</a>
<a class="sidebar-link admin-link {% if active_nav == 'paper_trading' %}active{% endif %}" href="/paper-trading" style="display:none"><svg class="link-icon"><use href="#svg-paper"/></svg>模拟交易</a>
<div class="sidebar-section-label">研究</div>
<a class="sidebar-link {% if active_nav == 'market' %}active{% endif %}" href="/market"><svg class="link-icon"><use href="#svg-target"/></svg>市场总览</a>
<a class="sidebar-link {% if active_nav == 'sentiment' %}active{% endif %}" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>实时舆情</a>

View File

@ -0,0 +1,62 @@
from datetime import datetime, timedelta
from fastapi.testclient import TestClient
from app.db import auth_db
from app.web import web_server
def _login_user(email: str, password: str = "StrongPass123", admin: bool = False) -> str:
reg = auth_db.register_user(email, password)
auth_db.verify_email(email, reg["verification_code"])
user = auth_db.get_user_by_email(email)
auth_db.claim_free_trial(user["id"])
if admin:
auth_db.set_user_admin(email, True)
return auth_db.login_user(email, password)["token"]
def test_paper_trading_page_requires_admin_for_normal_subscriber():
token = _login_user("normal-paper@example.com")
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
resp = client.get("/paper-trading")
assert resp.status_code == 403
assert "需要管理员权限" in resp.text
def test_paper_trading_api_requires_admin_for_normal_subscriber():
token = _login_user("normal-api-paper@example.com")
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
summary = client.get("/api/paper-trading/summary")
trades = client.get("/api/paper-trading/trades")
assert summary.status_code == 403
assert trades.status_code == 403
def test_paper_trading_admin_can_access_page_and_api():
token = _login_user("admin-paper@example.com", admin=True)
client = TestClient(web_server.app)
client.cookies.set("altcoin_session", token)
page = client.get("/paper-trading")
summary = client.get("/api/paper-trading/summary")
assert page.status_code == 200
assert "模拟交易" in page.text
assert summary.status_code == 200
assert "account_equity_usdt" in summary.json()
def test_sidebar_hides_paper_trading_with_admin_link_class():
client = TestClient(web_server.app)
resp = client.get("/app")
assert resp.status_code == 200
assert 'href="/paper-trading" style="display:none"' in resp.text
assert 'admin-link' in resp.text