| | """Admin API routes""" |
| | from fastapi import APIRouter, Depends, HTTPException, Header |
| | 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 ..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)""" |
| | await proxy_manager.update_proxy_config(request.proxy_enabled, request.proxy_url) |
| | return {"success": True, "message": "代理配置更新成功"} |
| |
|
| |
|
| | @router.post("/api/config/proxy") |
| | async def update_proxy_config( |
| | request: ProxyConfigRequest, |
| | token: str = Depends(verify_admin_token) |
| | ): |
| | """Update proxy configuration""" |
| | await proxy_manager.update_proxy_config(request.proxy_enabled, request.proxy_url) |
| | return {"success": True, "message": "代理配置更新成功"} |
| |
|
| |
|
| | @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") |
| | } for log in logs] |
| |
|
| |
|
| | @router.get("/api/admin/config") |
| | async def get_admin_config(token: str = Depends(verify_admin_token)): |
| | """Get admin configuration""" |
| | from ..core.config import config |
| |
|
| | 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)""" |
| | |
| | await db.update_admin_config(error_ban_threshold=request.error_ban_threshold) |
| |
|
| | return {"success": True, "message": "配置更新成功"} |
| |
|
| |
|
| | @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: |
| | |
| | await db.update_debug_config(enabled=request.enabled) |
| |
|
| | |
| | await db.reload_config_to_memory() |
| |
|
| | 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""" |
| | await db.update_generation_config(request.image_timeout, request.video_timeout) |
| |
|
| | |
| | await db.reload_config_to_memory() |
| |
|
| | return {"success": True, "message": "生成配置更新成功"} |
| |
|
| |
|
| | |
| |
|
| | @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""" |
| | 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": "缓存配置更新成功"} |
| |
|
| |
|
| | @router.post("/api/cache/base-url") |
| | async def update_cache_base_url( |
| | request: dict, |
| | token: str = Depends(verify_admin_token) |
| | ): |
| | """Update cache base URL""" |
| | 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更新成功"} |
| |
|
| |
|
| | @router.post("/api/captcha/config") |
| | async def update_captcha_config( |
| | request: dict, |
| | token: str = Depends(verify_admin_token) |
| | ): |
| | """Update captcha configuration""" |
| | 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": "验证码配置更新成功"} |
| |
|
| |
|
| | @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(token: str = Depends(verify_admin_token)): |
| | """Get plugin configuration""" |
| | plugin_config = await db.get_plugin_config() |
| |
|
| | |
| | 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 |
| | } |
| | } |
| |
|
| |
|
| | @router.post("/api/plugin/config") |
| | async def update_plugin_config( |
| | request: dict, |
| | token: str = Depends(verify_admin_token) |
| | ): |
| | """Update plugin configuration""" |
| | connection_token = request.get("connection_token", "") |
| |
|
| | |
| | if not connection_token: |
| | connection_token = secrets.token_urlsafe(32) |
| |
|
| | await db.update_plugin_config(connection_token=connection_token) |
| |
|
| | return { |
| | "success": True, |
| | "message": "插件配置更新成功", |
| | "connection_token": connection_token |
| | } |
| |
|
| |
|
| | @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 |
| | ) |
| |
|
| | 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)}") |
| |
|