|
|
"""Admin API routes""" |
|
|
from fastapi import APIRouter, Depends, HTTPException, Header, Request |
|
|
from fastapi.responses import JSONResponse |
|
|
from pydantic import BaseModel |
|
|
from typing import Optional, List |
|
|
import secrets |
|
|
from ..core.auth import AuthManager |
|
|
from ..core.database import Database |
|
|
from ..core.config import config |
|
|
from ..services.token_manager import TokenManager |
|
|
from ..services.proxy_manager import ProxyManager |
|
|
|
|
|
router = APIRouter() |
|
|
|
|
|
|
|
|
token_manager: TokenManager = None |
|
|
proxy_manager: ProxyManager = None |
|
|
db: Database = None |
|
|
|
|
|
|
|
|
active_admin_tokens = set() |
|
|
|
|
|
|
|
|
def set_dependencies(tm: TokenManager, pm: ProxyManager, database: Database): |
|
|
"""Set service instances""" |
|
|
global token_manager, proxy_manager, db |
|
|
token_manager = tm |
|
|
proxy_manager = pm |
|
|
db = database |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginRequest(BaseModel): |
|
|
username: str |
|
|
password: str |
|
|
|
|
|
|
|
|
class AddTokenRequest(BaseModel): |
|
|
st: str |
|
|
project_id: Optional[str] = None |
|
|
project_name: Optional[str] = None |
|
|
remark: Optional[str] = None |
|
|
image_enabled: bool = True |
|
|
video_enabled: bool = True |
|
|
image_concurrency: int = -1 |
|
|
video_concurrency: int = -1 |
|
|
|
|
|
|
|
|
class UpdateTokenRequest(BaseModel): |
|
|
st: str |
|
|
project_id: Optional[str] = None |
|
|
project_name: Optional[str] = None |
|
|
remark: Optional[str] = None |
|
|
image_enabled: Optional[bool] = None |
|
|
video_enabled: Optional[bool] = None |
|
|
image_concurrency: Optional[int] = None |
|
|
video_concurrency: Optional[int] = None |
|
|
|
|
|
|
|
|
class ProxyConfigRequest(BaseModel): |
|
|
proxy_enabled: bool |
|
|
proxy_url: Optional[str] = None |
|
|
|
|
|
|
|
|
class GenerationConfigRequest(BaseModel): |
|
|
image_timeout: int |
|
|
video_timeout: int |
|
|
|
|
|
|
|
|
class ChangePasswordRequest(BaseModel): |
|
|
username: Optional[str] = None |
|
|
old_password: str |
|
|
new_password: str |
|
|
|
|
|
|
|
|
class UpdateAPIKeyRequest(BaseModel): |
|
|
new_api_key: str |
|
|
|
|
|
|
|
|
class UpdateDebugConfigRequest(BaseModel): |
|
|
enabled: bool |
|
|
|
|
|
|
|
|
class UpdateAdminConfigRequest(BaseModel): |
|
|
error_ban_threshold: int |
|
|
|
|
|
|
|
|
class ST2ATRequest(BaseModel): |
|
|
"""ST转AT请求""" |
|
|
st: str |
|
|
|
|
|
|
|
|
class ImportTokenItem(BaseModel): |
|
|
"""导入Token项""" |
|
|
email: Optional[str] = None |
|
|
access_token: Optional[str] = None |
|
|
session_token: Optional[str] = None |
|
|
is_active: bool = True |
|
|
image_enabled: bool = True |
|
|
video_enabled: bool = True |
|
|
image_concurrency: int = -1 |
|
|
video_concurrency: int = -1 |
|
|
|
|
|
|
|
|
class ImportTokensRequest(BaseModel): |
|
|
"""导入Token请求""" |
|
|
tokens: List[ImportTokenItem] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def verify_admin_token(authorization: str = Header(None)): |
|
|
"""Verify admin session token (NOT API key)""" |
|
|
if not authorization or not authorization.startswith("Bearer "): |
|
|
raise HTTPException(status_code=401, detail="Missing authorization") |
|
|
|
|
|
token = authorization[7:] |
|
|
|
|
|
|
|
|
if token not in active_admin_tokens: |
|
|
raise HTTPException(status_code=401, detail="Invalid or expired admin token") |
|
|
|
|
|
return token |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/admin/login") |
|
|
async def admin_login(request: LoginRequest): |
|
|
"""Admin login - returns session token (NOT API key)""" |
|
|
admin_config = await db.get_admin_config() |
|
|
|
|
|
if not AuthManager.verify_admin(request.username, request.password): |
|
|
raise HTTPException(status_code=401, detail="Invalid credentials") |
|
|
|
|
|
|
|
|
session_token = f"admin-{secrets.token_urlsafe(32)}" |
|
|
|
|
|
|
|
|
active_admin_tokens.add(session_token) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"token": session_token, |
|
|
"username": admin_config.username |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/admin/logout") |
|
|
async def admin_logout(token: str = Depends(verify_admin_token)): |
|
|
"""Admin logout - invalidate session token""" |
|
|
active_admin_tokens.discard(token) |
|
|
return {"success": True, "message": "退出登录成功"} |
|
|
|
|
|
|
|
|
@router.post("/api/admin/change-password") |
|
|
async def change_password( |
|
|
request: ChangePasswordRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Change admin password""" |
|
|
admin_config = await db.get_admin_config() |
|
|
|
|
|
|
|
|
if not AuthManager.verify_admin(admin_config.username, request.old_password): |
|
|
raise HTTPException(status_code=400, detail="旧密码错误") |
|
|
|
|
|
|
|
|
update_params = {"password": request.new_password} |
|
|
if request.username: |
|
|
update_params["username"] = request.username |
|
|
|
|
|
await db.update_admin_config(**update_params) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
|
|
|
active_admin_tokens.clear() |
|
|
|
|
|
return {"success": True, "message": "密码修改成功,请重新登录"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/tokens") |
|
|
async def get_tokens(token: str = Depends(verify_admin_token)): |
|
|
"""Get all tokens with statistics""" |
|
|
tokens = await token_manager.get_all_tokens() |
|
|
result = [] |
|
|
|
|
|
for t in tokens: |
|
|
stats = await db.get_token_stats(t.id) |
|
|
|
|
|
result.append({ |
|
|
"id": t.id, |
|
|
"st": t.st, |
|
|
"at": t.at, |
|
|
"at_expires": t.at_expires.isoformat() if t.at_expires else None, |
|
|
"token": t.at, |
|
|
"email": t.email, |
|
|
"name": t.name, |
|
|
"remark": t.remark, |
|
|
"is_active": t.is_active, |
|
|
"created_at": t.created_at.isoformat() if t.created_at else None, |
|
|
"last_used_at": t.last_used_at.isoformat() if t.last_used_at else None, |
|
|
"use_count": t.use_count, |
|
|
"credits": t.credits, |
|
|
"user_paygate_tier": t.user_paygate_tier, |
|
|
"current_project_id": t.current_project_id, |
|
|
"current_project_name": t.current_project_name, |
|
|
"image_enabled": t.image_enabled, |
|
|
"video_enabled": t.video_enabled, |
|
|
"image_concurrency": t.image_concurrency, |
|
|
"video_concurrency": t.video_concurrency, |
|
|
"image_count": stats.image_count if stats else 0, |
|
|
"video_count": stats.video_count if stats else 0, |
|
|
"error_count": stats.error_count if stats else 0 |
|
|
}) |
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
@router.post("/api/tokens") |
|
|
async def add_token( |
|
|
request: AddTokenRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Add a new token""" |
|
|
try: |
|
|
new_token = await token_manager.add_token( |
|
|
st=request.st, |
|
|
project_id=request.project_id, |
|
|
project_name=request.project_name, |
|
|
remark=request.remark, |
|
|
image_enabled=request.image_enabled, |
|
|
video_enabled=request.video_enabled, |
|
|
image_concurrency=request.image_concurrency, |
|
|
video_concurrency=request.video_concurrency |
|
|
) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": "Token添加成功", |
|
|
"token": { |
|
|
"id": new_token.id, |
|
|
"email": new_token.email, |
|
|
"credits": new_token.credits, |
|
|
"project_id": new_token.current_project_id, |
|
|
"project_name": new_token.current_project_name |
|
|
} |
|
|
} |
|
|
except ValueError as e: |
|
|
raise HTTPException(status_code=400, detail=str(e)) |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"添加Token失败: {str(e)}") |
|
|
|
|
|
|
|
|
@router.put("/api/tokens/{token_id}") |
|
|
async def update_token( |
|
|
token_id: int, |
|
|
request: UpdateTokenRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update token - 使用ST自动刷新AT""" |
|
|
try: |
|
|
|
|
|
result = await token_manager.flow_client.st_to_at(request.st) |
|
|
at = result["access_token"] |
|
|
expires = result.get("expires") |
|
|
|
|
|
|
|
|
from datetime import datetime |
|
|
at_expires = None |
|
|
if expires: |
|
|
try: |
|
|
at_expires = datetime.fromisoformat(expires.replace('Z', '+00:00')) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
await token_manager.update_token( |
|
|
token_id=token_id, |
|
|
st=request.st, |
|
|
at=at, |
|
|
at_expires=at_expires, |
|
|
project_id=request.project_id, |
|
|
project_name=request.project_name, |
|
|
remark=request.remark, |
|
|
image_enabled=request.image_enabled, |
|
|
video_enabled=request.video_enabled, |
|
|
image_concurrency=request.image_concurrency, |
|
|
video_concurrency=request.video_concurrency |
|
|
) |
|
|
|
|
|
return {"success": True, "message": "Token更新成功"} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.delete("/api/tokens/{token_id}") |
|
|
async def delete_token( |
|
|
token_id: int, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Delete token""" |
|
|
try: |
|
|
await token_manager.delete_token(token_id) |
|
|
return {"success": True, "message": "Token删除成功"} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/{token_id}/enable") |
|
|
async def enable_token( |
|
|
token_id: int, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Enable token""" |
|
|
await token_manager.enable_token(token_id) |
|
|
return {"success": True, "message": "Token已启用"} |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/{token_id}/disable") |
|
|
async def disable_token( |
|
|
token_id: int, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Disable token""" |
|
|
await token_manager.disable_token(token_id) |
|
|
return {"success": True, "message": "Token已禁用"} |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/{token_id}/refresh-credits") |
|
|
async def refresh_credits( |
|
|
token_id: int, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""刷新Token余额 🆕""" |
|
|
try: |
|
|
credits = await token_manager.refresh_credits(token_id) |
|
|
return { |
|
|
"success": True, |
|
|
"message": "余额刷新成功", |
|
|
"credits": credits |
|
|
} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"刷新余额失败: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/{token_id}/refresh-at") |
|
|
async def refresh_at( |
|
|
token_id: int, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""手动刷新Token的AT (使用ST转换) 🆕""" |
|
|
try: |
|
|
|
|
|
success = await token_manager._refresh_at(token_id) |
|
|
|
|
|
if success: |
|
|
|
|
|
updated_token = await token_manager.get_token(token_id) |
|
|
return { |
|
|
"success": True, |
|
|
"message": "AT刷新成功", |
|
|
"token": { |
|
|
"id": updated_token.id, |
|
|
"email": updated_token.email, |
|
|
"at_expires": updated_token.at_expires.isoformat() if updated_token.at_expires else None |
|
|
} |
|
|
} |
|
|
else: |
|
|
raise HTTPException(status_code=500, detail="AT刷新失败") |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"刷新AT失败: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/st2at") |
|
|
async def st_to_at( |
|
|
request: ST2ATRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Convert Session Token to Access Token (仅转换,不添加到数据库)""" |
|
|
try: |
|
|
result = await token_manager.flow_client.st_to_at(request.st) |
|
|
return { |
|
|
"success": True, |
|
|
"message": "ST converted to AT successfully", |
|
|
"access_token": result["access_token"], |
|
|
"email": result.get("user", {}).get("email"), |
|
|
"expires": result.get("expires") |
|
|
} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/import") |
|
|
async def import_tokens( |
|
|
request: ImportTokensRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""批量导入Token""" |
|
|
from datetime import datetime, timezone |
|
|
|
|
|
added = 0 |
|
|
updated = 0 |
|
|
errors = [] |
|
|
|
|
|
for idx, item in enumerate(request.tokens): |
|
|
try: |
|
|
st = item.session_token |
|
|
|
|
|
if not st: |
|
|
errors.append(f"第{idx+1}项: 缺少 session_token") |
|
|
continue |
|
|
|
|
|
|
|
|
try: |
|
|
result = await token_manager.flow_client.st_to_at(st) |
|
|
at = result["access_token"] |
|
|
email = result.get("user", {}).get("email") |
|
|
expires = result.get("expires") |
|
|
|
|
|
if not email: |
|
|
errors.append(f"第{idx+1}项: 无法获取邮箱信息") |
|
|
continue |
|
|
|
|
|
|
|
|
at_expires = None |
|
|
is_expired = False |
|
|
if expires: |
|
|
try: |
|
|
at_expires = datetime.fromisoformat(expires.replace('Z', '+00:00')) |
|
|
|
|
|
now = datetime.now(timezone.utc) |
|
|
is_expired = at_expires <= now |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
existing_tokens = await token_manager.get_all_tokens() |
|
|
existing = next((t for t in existing_tokens if t.email == email), None) |
|
|
|
|
|
if existing: |
|
|
|
|
|
await token_manager.update_token( |
|
|
token_id=existing.id, |
|
|
st=st, |
|
|
at=at, |
|
|
at_expires=at_expires, |
|
|
image_enabled=item.image_enabled, |
|
|
video_enabled=item.video_enabled, |
|
|
image_concurrency=item.image_concurrency, |
|
|
video_concurrency=item.video_concurrency |
|
|
) |
|
|
|
|
|
if is_expired: |
|
|
await token_manager.disable_token(existing.id) |
|
|
updated += 1 |
|
|
else: |
|
|
|
|
|
new_token = await token_manager.add_token( |
|
|
st=st, |
|
|
image_enabled=item.image_enabled, |
|
|
video_enabled=item.video_enabled, |
|
|
image_concurrency=item.image_concurrency, |
|
|
video_concurrency=item.video_concurrency |
|
|
) |
|
|
|
|
|
if is_expired: |
|
|
await token_manager.disable_token(new_token.id) |
|
|
added += 1 |
|
|
|
|
|
except Exception as e: |
|
|
errors.append(f"第{idx+1}项: {str(e)}") |
|
|
|
|
|
except Exception as e: |
|
|
errors.append(f"第{idx+1}项: {str(e)}") |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"added": added, |
|
|
"updated": updated, |
|
|
"errors": errors if errors else None, |
|
|
"message": f"导入完成: 新增 {added} 个, 更新 {updated} 个" + (f", {len(errors)} 个失败" if errors else "") |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/config/proxy") |
|
|
async def get_proxy_config(token: str = Depends(verify_admin_token)): |
|
|
"""Get proxy configuration""" |
|
|
config = await proxy_manager.get_proxy_config() |
|
|
return { |
|
|
"success": True, |
|
|
"config": { |
|
|
"enabled": config.enabled, |
|
|
"proxy_url": config.proxy_url |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@router.get("/api/proxy/config") |
|
|
async def get_proxy_config_alias(token: str = Depends(verify_admin_token)): |
|
|
"""Get proxy configuration (alias for frontend compatibility)""" |
|
|
config = await proxy_manager.get_proxy_config() |
|
|
return { |
|
|
"proxy_enabled": config.enabled, |
|
|
"proxy_url": config.proxy_url |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/proxy/config") |
|
|
async def update_proxy_config_alias( |
|
|
request: ProxyConfigRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update proxy configuration (alias for frontend compatibility)""" |
|
|
try: |
|
|
await proxy_manager.update_proxy_config(request.proxy_enabled, request.proxy_url) |
|
|
return {"success": True, "message": "代理配置更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating proxy config: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.post("/api/config/proxy") |
|
|
async def update_proxy_config( |
|
|
request: ProxyConfigRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update proxy configuration""" |
|
|
try: |
|
|
await proxy_manager.update_proxy_config(request.proxy_enabled, request.proxy_url) |
|
|
return {"success": True, "message": "代理配置更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating proxy config: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.get("/api/config/generation") |
|
|
async def get_generation_config(token: str = Depends(verify_admin_token)): |
|
|
"""Get generation timeout configuration""" |
|
|
config = await db.get_generation_config() |
|
|
return { |
|
|
"success": True, |
|
|
"config": { |
|
|
"image_timeout": config.image_timeout, |
|
|
"video_timeout": config.video_timeout |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/config/generation") |
|
|
async def update_generation_config( |
|
|
request: GenerationConfigRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update generation timeout configuration""" |
|
|
await db.update_generation_config(request.image_timeout, request.video_timeout) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "生成配置更新成功"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/system/info") |
|
|
async def get_system_info(token: str = Depends(verify_admin_token)): |
|
|
"""Get system information""" |
|
|
tokens = await token_manager.get_all_tokens() |
|
|
active_tokens = [t for t in tokens if t.is_active] |
|
|
|
|
|
total_credits = sum(t.credits for t in active_tokens) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"info": { |
|
|
"total_tokens": len(tokens), |
|
|
"active_tokens": len(active_tokens), |
|
|
"total_credits": total_credits, |
|
|
"version": "1.0.0" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/login") |
|
|
async def login(request: LoginRequest): |
|
|
"""Login endpoint (alias for /api/admin/login)""" |
|
|
return await admin_login(request) |
|
|
|
|
|
|
|
|
@router.post("/api/logout") |
|
|
async def logout(token: str = Depends(verify_admin_token)): |
|
|
"""Logout endpoint (alias for /api/admin/logout)""" |
|
|
return await admin_logout(token) |
|
|
|
|
|
|
|
|
@router.get("/api/stats") |
|
|
async def get_stats(token: str = Depends(verify_admin_token)): |
|
|
"""Get statistics for dashboard""" |
|
|
tokens = await token_manager.get_all_tokens() |
|
|
active_tokens = [t for t in tokens if t.is_active] |
|
|
|
|
|
|
|
|
total_images = 0 |
|
|
total_videos = 0 |
|
|
total_errors = 0 |
|
|
today_images = 0 |
|
|
today_videos = 0 |
|
|
today_errors = 0 |
|
|
|
|
|
for t in tokens: |
|
|
stats = await db.get_token_stats(t.id) |
|
|
if stats: |
|
|
total_images += stats.image_count |
|
|
total_videos += stats.video_count |
|
|
total_errors += stats.error_count |
|
|
today_images += stats.today_image_count |
|
|
today_videos += stats.today_video_count |
|
|
today_errors += stats.today_error_count |
|
|
|
|
|
return { |
|
|
"total_tokens": len(tokens), |
|
|
"active_tokens": len(active_tokens), |
|
|
"total_images": total_images, |
|
|
"total_videos": total_videos, |
|
|
"total_errors": total_errors, |
|
|
"today_images": today_images, |
|
|
"today_videos": today_videos, |
|
|
"today_errors": today_errors |
|
|
} |
|
|
|
|
|
|
|
|
@router.get("/api/logs") |
|
|
async def get_logs( |
|
|
limit: int = 100, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Get request logs with token email""" |
|
|
logs = await db.get_logs(limit=limit) |
|
|
|
|
|
return [{ |
|
|
"id": log.get("id"), |
|
|
"token_id": log.get("token_id"), |
|
|
"token_email": log.get("token_email"), |
|
|
"token_username": log.get("token_username"), |
|
|
"operation": log.get("operation"), |
|
|
"status_code": log.get("status_code"), |
|
|
"duration": log.get("duration"), |
|
|
"created_at": log.get("created_at"), |
|
|
"request_body": log.get("request_body"), |
|
|
"response_body": log.get("response_body") |
|
|
} for log in logs] |
|
|
|
|
|
|
|
|
@router.delete("/api/logs") |
|
|
async def clear_logs(token: str = Depends(verify_admin_token)): |
|
|
"""Clear all logs""" |
|
|
try: |
|
|
await db.clear_all_logs() |
|
|
return {"success": True, "message": "所有日志已清空"} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/api/admin/config") |
|
|
async def get_admin_config(token: str = Depends(verify_admin_token)): |
|
|
"""Get admin configuration""" |
|
|
admin_config = await db.get_admin_config() |
|
|
|
|
|
return { |
|
|
"admin_username": admin_config.username, |
|
|
"api_key": admin_config.api_key, |
|
|
"error_ban_threshold": admin_config.error_ban_threshold, |
|
|
"debug_enabled": config.debug_enabled |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/admin/config") |
|
|
async def update_admin_config( |
|
|
request: UpdateAdminConfigRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update admin configuration (error_ban_threshold)""" |
|
|
try: |
|
|
|
|
|
await db.update_admin_config(error_ban_threshold=request.error_ban_threshold) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "配置更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating admin config: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.post("/api/admin/password") |
|
|
async def update_admin_password( |
|
|
request: ChangePasswordRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update admin password""" |
|
|
return await change_password(request, token) |
|
|
|
|
|
|
|
|
@router.post("/api/admin/apikey") |
|
|
async def update_api_key( |
|
|
request: UpdateAPIKeyRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update API key (for external API calls, NOT for admin login)""" |
|
|
|
|
|
await db.update_admin_config(api_key=request.new_api_key) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "API Key更新成功"} |
|
|
|
|
|
|
|
|
@router.post("/api/admin/debug") |
|
|
async def update_debug_config( |
|
|
request: UpdateDebugConfigRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update debug configuration""" |
|
|
try: |
|
|
|
|
|
|
|
|
config.set_debug_enabled(request.enabled) |
|
|
|
|
|
status = "enabled" if request.enabled else "disabled" |
|
|
return {"success": True, "message": f"Debug mode {status}", "enabled": request.enabled} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to update debug config: {str(e)}") |
|
|
|
|
|
|
|
|
@router.get("/api/generation/timeout") |
|
|
async def get_generation_timeout(token: str = Depends(verify_admin_token)): |
|
|
"""Get generation timeout configuration""" |
|
|
return await get_generation_config(token) |
|
|
|
|
|
|
|
|
@router.post("/api/generation/timeout") |
|
|
async def update_generation_timeout( |
|
|
request: GenerationConfigRequest, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update generation timeout configuration""" |
|
|
try: |
|
|
await db.update_generation_config(request.image_timeout, request.video_timeout) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "生成配置更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating generation timeout: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/token-refresh/config") |
|
|
async def get_token_refresh_config(token: str = Depends(verify_admin_token)): |
|
|
"""Get AT auto refresh configuration (默认启用)""" |
|
|
return { |
|
|
"success": True, |
|
|
"config": { |
|
|
"at_auto_refresh_enabled": True |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/token-refresh/enabled") |
|
|
async def update_token_refresh_enabled( |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update AT auto refresh enabled (Flow2API固定启用,此接口仅用于前端兼容)""" |
|
|
return { |
|
|
"success": True, |
|
|
"message": "Flow2API的AT自动刷新默认启用且无法关闭" |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/cache/config") |
|
|
async def get_cache_config(token: str = Depends(verify_admin_token)): |
|
|
"""Get cache configuration""" |
|
|
cache_config = await db.get_cache_config() |
|
|
|
|
|
|
|
|
effective_base_url = cache_config.cache_base_url if cache_config.cache_base_url else f"http://127.0.0.1:8000" |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"config": { |
|
|
"enabled": cache_config.cache_enabled, |
|
|
"timeout": cache_config.cache_timeout, |
|
|
"base_url": cache_config.cache_base_url or "", |
|
|
"effective_base_url": effective_base_url |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/cache/enabled") |
|
|
async def update_cache_enabled( |
|
|
request: dict, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update cache enabled status""" |
|
|
enabled = request.get("enabled", False) |
|
|
await db.update_cache_config(enabled=enabled) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": f"缓存已{'启用' if enabled else '禁用'}"} |
|
|
|
|
|
|
|
|
@router.post("/api/cache/config") |
|
|
async def update_cache_config_full( |
|
|
request: dict, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update complete cache configuration""" |
|
|
try: |
|
|
enabled = request.get("enabled") |
|
|
timeout = request.get("timeout") |
|
|
base_url = request.get("base_url") |
|
|
|
|
|
await db.update_cache_config(enabled=enabled, timeout=timeout, base_url=base_url) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "缓存配置更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating cache config: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.post("/api/cache/base-url") |
|
|
async def update_cache_base_url( |
|
|
request: dict, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update cache base URL""" |
|
|
try: |
|
|
base_url = request.get("base_url", "") |
|
|
await db.update_cache_config(base_url=base_url) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "缓存Base URL更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating cache base URL: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.post("/api/captcha/config") |
|
|
async def update_captcha_config( |
|
|
request: dict, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update captcha configuration""" |
|
|
try: |
|
|
from ..services.browser_captcha import validate_browser_proxy_url |
|
|
|
|
|
captcha_method = request.get("captcha_method") |
|
|
yescaptcha_api_key = request.get("yescaptcha_api_key") |
|
|
yescaptcha_base_url = request.get("yescaptcha_base_url") |
|
|
browser_proxy_enabled = request.get("browser_proxy_enabled", False) |
|
|
browser_proxy_url = request.get("browser_proxy_url", "") |
|
|
|
|
|
|
|
|
if browser_proxy_enabled and browser_proxy_url: |
|
|
is_valid, error_msg = validate_browser_proxy_url(browser_proxy_url) |
|
|
if not is_valid: |
|
|
return {"success": False, "message": error_msg} |
|
|
|
|
|
await db.update_captcha_config( |
|
|
captcha_method=captcha_method, |
|
|
yescaptcha_api_key=yescaptcha_api_key, |
|
|
yescaptcha_base_url=yescaptcha_base_url, |
|
|
browser_proxy_enabled=browser_proxy_enabled, |
|
|
browser_proxy_url=browser_proxy_url if browser_proxy_enabled else None |
|
|
) |
|
|
|
|
|
|
|
|
await db.reload_config_to_memory() |
|
|
|
|
|
return {"success": True, "message": "验证码配置更新成功"} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating captcha config: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.get("/api/captcha/config") |
|
|
async def get_captcha_config(token: str = Depends(verify_admin_token)): |
|
|
"""Get captcha configuration""" |
|
|
captcha_config = await db.get_captcha_config() |
|
|
return { |
|
|
"captcha_method": captcha_config.captcha_method, |
|
|
"yescaptcha_api_key": captcha_config.yescaptcha_api_key, |
|
|
"yescaptcha_base_url": captcha_config.yescaptcha_base_url, |
|
|
"browser_proxy_enabled": captcha_config.browser_proxy_enabled, |
|
|
"browser_proxy_url": captcha_config.browser_proxy_url or "" |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/api/plugin/config") |
|
|
async def get_plugin_config(request: Request, token: str = Depends(verify_admin_token)): |
|
|
"""Get plugin configuration""" |
|
|
plugin_config = await db.get_plugin_config() |
|
|
|
|
|
|
|
|
|
|
|
host_header = request.headers.get("host", "") |
|
|
|
|
|
|
|
|
if host_header: |
|
|
|
|
|
connection_url = f"http://{host_header}/api/plugin/update-token" |
|
|
else: |
|
|
|
|
|
from ..core.config import config |
|
|
server_host = config.server_host |
|
|
server_port = config.server_port |
|
|
|
|
|
if server_host == "0.0.0.0": |
|
|
connection_url = f"http://127.0.0.1:{server_port}/api/plugin/update-token" |
|
|
else: |
|
|
connection_url = f"http://{server_host}:{server_port}/api/plugin/update-token" |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"config": { |
|
|
"connection_token": plugin_config.connection_token, |
|
|
"connection_url": connection_url, |
|
|
"auto_enable_on_update": plugin_config.auto_enable_on_update |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@router.post("/api/plugin/config") |
|
|
async def update_plugin_config( |
|
|
request: dict, |
|
|
token: str = Depends(verify_admin_token) |
|
|
): |
|
|
"""Update plugin configuration""" |
|
|
try: |
|
|
connection_token = request.get("connection_token", "") |
|
|
auto_enable_on_update = request.get("auto_enable_on_update", True) |
|
|
|
|
|
|
|
|
if not connection_token: |
|
|
connection_token = secrets.token_urlsafe(32) |
|
|
|
|
|
await db.update_plugin_config( |
|
|
connection_token=connection_token, |
|
|
auto_enable_on_update=auto_enable_on_update |
|
|
) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": "插件配置更新成功", |
|
|
"connection_token": connection_token, |
|
|
"auto_enable_on_update": auto_enable_on_update |
|
|
} |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Error updating plugin config: {e}") |
|
|
return {"success": False, "message": f"保存失败: {str(e)}"} |
|
|
|
|
|
|
|
|
@router.post("/api/plugin/update-token") |
|
|
async def plugin_update_token(request: dict, authorization: Optional[str] = Header(None)): |
|
|
"""Receive token update from Chrome extension (no admin auth required, uses connection_token)""" |
|
|
|
|
|
plugin_config = await db.get_plugin_config() |
|
|
|
|
|
|
|
|
provided_token = None |
|
|
if authorization: |
|
|
if authorization.startswith("Bearer "): |
|
|
provided_token = authorization[7:] |
|
|
else: |
|
|
provided_token = authorization |
|
|
|
|
|
|
|
|
if not plugin_config.connection_token or provided_token != plugin_config.connection_token: |
|
|
raise HTTPException(status_code=401, detail="Invalid connection token") |
|
|
|
|
|
|
|
|
session_token = request.get("session_token") |
|
|
|
|
|
if not session_token: |
|
|
raise HTTPException(status_code=400, detail="Missing session_token") |
|
|
|
|
|
|
|
|
try: |
|
|
result = await token_manager.flow_client.st_to_at(session_token) |
|
|
at = result["access_token"] |
|
|
expires = result.get("expires") |
|
|
user_info = result.get("user", {}) |
|
|
email = user_info.get("email", "") |
|
|
|
|
|
if not email: |
|
|
raise HTTPException(status_code=400, detail="Failed to get email from session token") |
|
|
|
|
|
|
|
|
from datetime import datetime |
|
|
at_expires = None |
|
|
if expires: |
|
|
try: |
|
|
at_expires = datetime.fromisoformat(expires.replace('Z', '+00:00')) |
|
|
except: |
|
|
pass |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=400, detail=f"Invalid session token: {str(e)}") |
|
|
|
|
|
|
|
|
existing_token = await db.get_token_by_email(email) |
|
|
|
|
|
if existing_token: |
|
|
|
|
|
try: |
|
|
|
|
|
await token_manager.update_token( |
|
|
token_id=existing_token.id, |
|
|
st=session_token, |
|
|
at=at, |
|
|
at_expires=at_expires |
|
|
) |
|
|
|
|
|
|
|
|
if plugin_config.auto_enable_on_update and not existing_token.is_active: |
|
|
await token_manager.enable_token(existing_token.id) |
|
|
return { |
|
|
"success": True, |
|
|
"message": f"Token updated and auto-enabled for {email}", |
|
|
"action": "updated", |
|
|
"auto_enabled": True |
|
|
} |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": f"Token updated for {email}", |
|
|
"action": "updated" |
|
|
} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to update token: {str(e)}") |
|
|
else: |
|
|
|
|
|
try: |
|
|
new_token = await token_manager.add_token( |
|
|
st=session_token, |
|
|
remark="Added by Chrome Extension" |
|
|
) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": f"Token added for {new_token.email}", |
|
|
"action": "added", |
|
|
"token_id": new_token.id |
|
|
} |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to add token: {str(e)}") |
|
|
|