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:
1 parent
66e8b633ea
commit
9ed9961b50
4 files changed
+80
-45
No files matched your search
@@ -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("""
|
||||
|
||||
@@ -441,6 +441,7 @@ app.include_router(promotion_admin.router, prefix="/app/api/admin/promotion", ta
|
||||
app.include_router(tenant_admin.router, prefix="/app/api/admin/tenant", tags=["tenant"])
|
||||
app.include_router(billing_admin.router, prefix="/app/api/admin/billing", tags=["billing"])
|
||||
app.include_router(analytics_admin.router, prefix="/app/api/admin/analytics", tags=["analytics"])
|
||||
app.include_router(user_portal.router, prefix="/app/api/portal", tags=["portal"])
|
||||
app.include_router(achievement_admin.router, prefix="/app/api/admin/achievement", tags=["achievement"])
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user