1
This commit is contained in:
parent
ce7a203f3d
commit
ca021d541e
@ -11,6 +11,7 @@ from app.web.shared import (
|
|||||||
SendCodeRequest,
|
SendCodeRequest,
|
||||||
VerifyEmailRequest,
|
VerifyEmailRequest,
|
||||||
auth_error,
|
auth_error,
|
||||||
|
has_active_subscription,
|
||||||
require_user,
|
require_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -108,6 +109,8 @@ async def api_auth_login(req: LoginRequest, request: Request = None):
|
|||||||
async def api_auth_me(altcoin_session: str = Cookie(default="")):
|
async def api_auth_me(altcoin_session: str = Cookie(default="")):
|
||||||
user = require_user(altcoin_session)
|
user = require_user(altcoin_session)
|
||||||
sub = auth_db.get_current_subscription(user["id"])
|
sub = auth_db.get_current_subscription(user["id"])
|
||||||
|
if not user.get("local_debug") and not has_active_subscription(user):
|
||||||
|
raise HTTPException(status_code=402, detail="订阅已过期或未开通,请先开通订阅")
|
||||||
return {"ok": True, "user": user, "subscription": sub, "subscription_active": bool(sub)}
|
return {"ok": True, "user": user, "subscription": sub, "subscription_active": bool(sub)}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -148,10 +148,10 @@ def subscription_redirect():
|
|||||||
|
|
||||||
|
|
||||||
def has_active_subscription(user) -> bool:
|
def has_active_subscription(user) -> bool:
|
||||||
if is_local_request():
|
|
||||||
return True
|
|
||||||
if not user:
|
if not user:
|
||||||
return False
|
return False
|
||||||
|
if user.get("local_debug"):
|
||||||
|
return True
|
||||||
try:
|
try:
|
||||||
if auth_db.is_user_admin(user["id"]):
|
if auth_db.is_user_admin(user["id"]):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -266,7 +266,6 @@ a { color: inherit; text-decoration: none; }
|
|||||||
<a class="sidebar-link {% if active_nav == 'subscription' %}active{% endif %}" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
|
<a class="sidebar-link {% if active_nav == 'subscription' %}active{% endif %}" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
|
||||||
<a class="sidebar-link {% if active_nav == 'referral' %}active{% endif %}" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>邀请</a>
|
<a class="sidebar-link {% if active_nav == 'referral' %}active{% endif %}" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>邀请</a>
|
||||||
<div class="sidebar-section-label admin-link" style="display:none">管理员菜单</div>
|
<div class="sidebar-section-label admin-link" style="display:none">管理员菜单</div>
|
||||||
<a class="sidebar-link admin-link {% if active_nav == 'operations' %}active{% endif %}" href="/operations" target="_blank" rel="noopener" style="display:none"><svg class="link-icon"><use href="#svg-operations"/></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>
|
<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>
|
||||||
<a class="sidebar-link admin-link {% if active_nav == 'live_trading' %}active{% endif %}" href="/live-trading" style="display:none"><svg class="link-icon"><use href="#svg-shield"/></svg>实盘控制台</a>
|
<a class="sidebar-link admin-link {% if active_nav == 'live_trading' %}active{% endif %}" href="/live-trading" style="display:none"><svg class="link-icon"><use href="#svg-shield"/></svg>实盘控制台</a>
|
||||||
<a class="sidebar-link admin-link {% if active_nav == 'review_center' %}active{% endif %}" href="/review-center" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>复盘中心</a>
|
<a class="sidebar-link admin-link {% if active_nav == 'review_center' %}active{% endif %}" href="/review-center" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>复盘中心</a>
|
||||||
@ -328,6 +327,8 @@ window.addEventListener('orientationchange', function(){ setTimeout(setAppViewpo
|
|||||||
async function loadUser() {
|
async function loadUser() {
|
||||||
try {
|
try {
|
||||||
var resp = await fetch(API + '/api/auth/me');
|
var resp = await fetch(API + '/api/auth/me');
|
||||||
|
if (resp.status === 401) { window.location.href = '/auth?tab=login'; return; }
|
||||||
|
if (resp.status === 402) { window.location.href = '/subscription?expired=1'; return; }
|
||||||
if (!resp.ok) return;
|
if (!resp.ok) return;
|
||||||
var data = await resp.json();
|
var data = await resp.json();
|
||||||
currentUser = data.user;
|
currentUser = data.user;
|
||||||
|
|||||||
@ -342,6 +342,7 @@ def test_sidebar_keeps_engineering_pages_in_admin_menu(temp_db):
|
|||||||
html = resp.text
|
html = resp.text
|
||||||
assert "机会中心" in html
|
assert "机会中心" in html
|
||||||
assert "诊断中心" in html
|
assert "诊断中心" in html
|
||||||
|
assert 'href="/operations"' not in html
|
||||||
assert 'href="/llm-insights"' not in html
|
assert 'href="/llm-insights"' not in html
|
||||||
assert 'href="/data-export"' not in html
|
assert 'href="/data-export"' not in html
|
||||||
assert 'href="/strategy"' not in html
|
assert 'href="/strategy"' not in html
|
||||||
|
|||||||
@ -223,12 +223,11 @@ def test_auth_page_hides_internal_requirements_and_has_modern_member_copy(temp_a
|
|||||||
assert forbidden not in html
|
assert forbidden not in html
|
||||||
|
|
||||||
for expected in [
|
for expected in [
|
||||||
"提前发现机会,别在强信号后追高",
|
|
||||||
"登录或开启免费体验",
|
|
||||||
"创建账号",
|
"创建账号",
|
||||||
"会员登录",
|
"会员登录",
|
||||||
"前往订阅中心",
|
"邮箱验证码",
|
||||||
"AlphaX Agent | Crypto",
|
"发送验证码",
|
||||||
|
"AlphaX Agent",
|
||||||
]:
|
]:
|
||||||
assert expected in html
|
assert expected in html
|
||||||
|
|
||||||
@ -251,17 +250,35 @@ def test_subscription_page_owns_trial_and_plan_flow(temp_auth_db):
|
|||||||
assert "USDT 订阅已预留表结构" not in html
|
assert "USDT 订阅已预留表结构" not in html
|
||||||
|
|
||||||
|
|
||||||
def test_app_shell_returns_200_for_all_users(temp_auth_db):
|
def test_app_shell_requires_active_subscription_for_real_users(temp_auth_db):
|
||||||
"""v2: /app 是纯壳页,不校验登录/订阅(鉴权由 JS 调用 /api/auth/me 完成)"""
|
|
||||||
client = TestClient(web_server.app)
|
client = TestClient(web_server.app)
|
||||||
# 未登录也能拿到壳页(JS自己判断跳转)
|
|
||||||
assert client.get("/app").status_code == 200
|
|
||||||
assert "AlphaX Agent | Crypto" in client.get("/app").text
|
|
||||||
|
|
||||||
# 登录用户也一样
|
|
||||||
reg = auth_db.register_user("alice@example.com", "StrongPass123")
|
reg = auth_db.register_user("alice@example.com", "StrongPass123")
|
||||||
auth_db.verify_email("alice@example.com", reg["verification_code"])
|
auth_db.verify_email("alice@example.com", reg["verification_code"])
|
||||||
login = auth_db.login_user("alice@example.com", "StrongPass123")
|
login = auth_db.login_user("alice@example.com", "StrongPass123")
|
||||||
token = login["token"]
|
token = login["token"]
|
||||||
client.cookies.set("altcoin_session", token)
|
client.cookies.set("altcoin_session", token)
|
||||||
|
|
||||||
|
expired_at = (datetime.now() - timedelta(days=1)).isoformat(timespec="seconds")
|
||||||
|
conn = auth_db.get_conn()
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO user_subscription (user_id, plan_code, start_at, end_at, status, source, order_id, created_at, updated_at)
|
||||||
|
VALUES (%s, 'free_trial_1m', %s, %s, 'active', 'test', 0, %s, %s)
|
||||||
|
""",
|
||||||
|
(login["user"]["id"], (datetime.now() - timedelta(days=31)).isoformat(timespec="seconds"), expired_at, expired_at, expired_at),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
page = client.get("/app", follow_redirects=False)
|
||||||
|
me = client.get("/api/auth/me")
|
||||||
|
|
||||||
|
assert page.status_code == 302
|
||||||
|
assert page.headers["location"] == "/subscription?expired=1"
|
||||||
|
assert me.status_code == 402
|
||||||
|
|
||||||
|
sub = auth_db.claim_free_trial(login["user"]["id"])
|
||||||
|
assert sub["status"] == "active"
|
||||||
assert client.get("/app").status_code == 200
|
assert client.get("/app").status_code == 200
|
||||||
|
assert client.get("/api/auth/me").status_code == 200
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user