Get all tokens with statistics
(token: str = Depends(verify_admin_token))
| 791 | |
| 792 | @router.get("/api/tokens") |
| 793 | async def get_tokens(token: str = Depends(verify_admin_token)): |
| 794 | """Get all tokens with statistics""" |
| 795 | token_rows = await db.get_all_tokens_with_stats() |
| 796 | to_iso = lambda value: value.isoformat() if hasattr(value, "isoformat") else value |
| 797 | now = datetime.now(timezone.utc) |
| 798 | |
| 799 | def normalize_dt(value): |
| 800 | if not value: |
| 801 | return None |
| 802 | if isinstance(value, str): |
| 803 | try: |
| 804 | value = datetime.fromisoformat(value.replace("Z", "+00:00")) |
| 805 | except Exception: |
| 806 | return None |
| 807 | if getattr(value, "tzinfo", None) is None: |
| 808 | return value.replace(tzinfo=timezone.utc) |
| 809 | return value.astimezone(timezone.utc) |
| 810 | |
| 811 | return [{ |
| 812 | "id": row.get("id"), |
| 813 | "st": row.get("st"), # Session Token for editing |
| 814 | "at": row.get("at"), # Access Token for editing (从ST转换而来) |
| 815 | "at_expires": to_iso(row.get("at_expires")) if row.get("at_expires") else None, # 🆕 AT过期时间 |
| 816 | "at_expired": bool(normalize_dt(row.get("at_expires")) and normalize_dt(row.get("at_expires")) <= now), |
| 817 | "at_expiring_within_1h": bool( |
| 818 | normalize_dt(row.get("at_expires")) |
| 819 | and normalize_dt(row.get("at_expires")) > now |
| 820 | and (normalize_dt(row.get("at_expires")) - now).total_seconds() < 3600 |
| 821 | ), |
| 822 | "token": row.get("at"), # 兼容前端 token.token 的访问方式 |
| 823 | "email": row.get("email"), |
| 824 | "name": row.get("name"), |
| 825 | "remark": row.get("remark"), |
| 826 | "is_active": bool(row.get("is_active")), |
| 827 | "created_at": to_iso(row.get("created_at")) if row.get("created_at") else None, |
| 828 | "last_used_at": to_iso(row.get("last_used_at")) if row.get("last_used_at") else None, |
| 829 | "use_count": row.get("use_count"), |
| 830 | "credits": row.get("credits"), # 🆕 余额 |
| 831 | "user_paygate_tier": row.get("user_paygate_tier"), |
| 832 | "current_project_id": row.get("current_project_id"), # 🆕 项目ID |
| 833 | "current_project_name": row.get("current_project_name"), # 🆕 项目名称 |
| 834 | "captcha_proxy_url": row.get("captcha_proxy_url") or "", |
| 835 | "extension_route_key": row.get("extension_route_key") or "", |
| 836 | "protocol_mode": row.get("protocol_mode") or "session", |
| 837 | "google_cookies": row.get("google_cookies") or "", |
| 838 | "login_account": row.get("login_account") or "", |
| 839 | "login_password": row.get("login_password") or "", |
| 840 | "proxy_url": row.get("proxy_url") or "", |
| 841 | "auto_refresh_enabled": bool(row.get("auto_refresh_enabled", True)), |
| 842 | "refresh_interval_minutes": row.get("refresh_interval_minutes") or 120, |
| 843 | "last_st_refresh_at": to_iso(row.get("last_st_refresh_at")) if row.get("last_st_refresh_at") else None, |
| 844 | "last_st_refresh_result": row.get("last_st_refresh_result") or "", |
| 845 | "image_enabled": bool(row.get("image_enabled")), |
| 846 | "video_enabled": bool(row.get("video_enabled")), |
| 847 | "image_concurrency": row.get("image_concurrency"), |
| 848 | "video_concurrency": row.get("video_concurrency"), |
| 849 | "image_count": row.get("image_count", 0), |
| 850 | "video_count": row.get("video_count", 0), |
nothing calls this directly
no test coverage detected