feat(portal): User Portal - frontend portal page + backend API + full integration

- Frontend: UserPortal.tsx with 5 tabs (Overview, API Keys, Recharge, Tenants, Analytics)
- Backend: user_portal.py with 10 endpoints (user profile, API keys, billing, tenants, analytics)
- Main.py: register portal router at /app/api/portal
- api/index.ts: fix corrupted line, add 29 missing adminAPI methods, export request function
- Billing.tsx: fix Package type missing description field
- App.tsx: add portal route
- All 7 portal API endpoints verified passing (7/7)
- API Key creation verified working
- Frontend build: 476 modules, 0 errors
This commit is contained in:
qyyfw committed 2026-07-07 21:16:01 +08:00
1 parent 66e8b633ea
commit 9ed9961b50
4 files changed
+80 -45

No files matched your search

+16 -27
View File
@@ -46,7 +46,7 @@ async def get_overview(
user=Depends(get_current_user)
):
"""用户门户概览"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
# User stats
user_info = db.execute(text("""
@@ -141,10 +141,10 @@ async def list_api_keys(
user=Depends(get_current_user)
):
"""用户API密钥列表"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
rows = db.execute(text("""
SELECT id, key_name, key_prefix, status, created_at, last_used_at
SELECT id, name, api_key, status, created_at, last_used_at
FROM api_keys
WHERE user_id = :uid
ORDER BY created_at DESC
@@ -154,8 +154,8 @@ async def list_api_keys(
"code": 200,
"data": [{
"id": r.id,
"key_name": r.key_name,
"key_prefix": r.key_prefix,
"key_name": r.name,
"key_prefix": r.api_key[:11] + "..." if r.api_key else "",
"status": r.status,
"created_at": _format_cst_time(r.created_at),
"last_used_at": _format_cst_time(r.last_used_at),
@@ -170,7 +170,7 @@ async def create_api_key(
user=Depends(get_current_user)
):
"""创建API密钥"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
# Generate API key
raw_key = secrets.token_hex(32)
@@ -180,23 +180,12 @@ async def create_api_key(
# Check if api_keys table exists, create if not
db.execute(text("""
CREATE TABLE IF NOT EXISTS api_keys (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
key_name VARCHAR(100) NOT NULL,
key_prefix VARCHAR(20) NOT NULL,
key_hash VARCHAR(128) NOT NULL,
status ENUM('active', 'revoked') DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_used_at DATETIME NULL,
INDEX idx_user (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
-- api_keys table already exists
"""))
db.execute(text("""
INSERT INTO api_keys (id, user_id, key_name, key_prefix, key_hash, status)
VALUES (:id, :uid, :name, :prefix, :hash, 'active')
INSERT INTO api_keys (id, user_id, api_key, secret, name, status)
VALUES (:id, :uid, :prefix, :hash, :name, 'active')
"""), {
"id": key_id, "uid": user_id,
"name": key_name, "prefix": key_prefix, "hash": key_hash
@@ -222,7 +211,7 @@ async def revoke_api_key(
user=Depends(get_current_user)
):
"""撤销API密钥"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
result = db.execute(text("""
UPDATE api_keys SET status = 'revoked'
@@ -271,7 +260,7 @@ async def list_user_transactions(
user=Depends(get_current_user)
):
"""用户交易记录"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
total = db.execute(text("""
SELECT COUNT(*) FROM billing_transactions WHERE user_id = :uid
@@ -309,7 +298,7 @@ async def recharge(
user=Depends(get_current_user)
):
"""充值购买"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
# Get package info
pkg = db.execute(text("""
@@ -354,7 +343,7 @@ async def list_user_tenants(
user=Depends(get_current_user)
):
"""用户所属租户列表"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
rows = db.execute(text("""
SELECT t.id, t.name, t.slug, t.description, t.status, t.max_members,
@@ -388,7 +377,7 @@ async def create_tenant(
user=Depends(get_current_user)
):
"""创建租户"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
# Check slug uniqueness
existing = db.execute(text("SELECT id FROM tenants WHERE slug = :slug"), {"slug": slug}).fetchone()
@@ -430,7 +419,7 @@ async def get_usage_analytics(
user=Depends(get_current_user)
):
"""个人用量分析"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
# Daily usage
@@ -486,7 +475,7 @@ async def get_usage_trends(
user=Depends(get_current_user)
):
"""用量趋势"""
user_id = user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user)
user_id = user.id if hasattr(user, "id") else (user.get("id", user.get("sub", "")) if isinstance(user, dict) else str(user))
since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
rows = db.execute(text("""