Release v1.5.0 and add ops admin dashboard

This commit is contained in:
2569718930@qq.com
2026-03-21 00:40:28 +08:00
parent 345c56c4f4
commit e7e52c0da7
22 changed files with 893 additions and 11 deletions
+23
View File
@@ -151,6 +151,11 @@ _SUPABASE_AUTH_REQUIRED = _env_bool(
"POLYWEATHER_AUTH_REQUIRED",
SUPABASE_ENTITLEMENT.enabled,
)
_OPS_ADMIN_EMAILS = {
item.strip().lower()
for item in str(os.getenv("POLYWEATHER_OPS_ADMIN_EMAILS") or "").split(",")
if item.strip()
}
def _legacy_service_token_valid(request: Request) -> bool:
@@ -287,6 +292,19 @@ def _require_supabase_identity(request: Request) -> Dict[str, str]:
raise HTTPException(status_code=401, detail="Unauthorized")
def _require_ops_admin(request: Request) -> Dict[str, str]:
identity = _require_supabase_identity(request)
if not _OPS_ADMIN_EMAILS:
raise HTTPException(
status_code=503,
detail="ops admin is not configured; set POLYWEATHER_OPS_ADMIN_EMAILS",
)
email = str(identity.get("email") or "").strip().lower()
if not email or email not in _OPS_ADMIN_EMAILS:
raise HTTPException(status_code=403, detail="ops admin required")
return identity
class WalletChallengeRequest(BaseModel):
address: str = Field(..., min_length=8)
@@ -320,6 +338,11 @@ class ConfirmPaymentTxRequest(BaseModel):
tx_hash: Optional[str] = None
class GrantPointsRequest(BaseModel):
email: str = Field(..., min_length=3)
points: int = Field(..., gt=0, le=100000)
def _sf(v) -> Optional[float]:
if v is None:
return None
+39
View File
@@ -25,6 +25,7 @@ from web.core import (
SUPABASE_ENTITLEMENT,
ConfirmPaymentTxRequest,
CreatePaymentIntentRequest,
GrantPointsRequest,
SubmitPaymentTxRequest,
WalletChallengeRequest,
WalletUnbindRequest,
@@ -33,6 +34,7 @@ from web.core import (
_SUPABASE_AUTH_REQUIRED,
_assert_entitlement,
_bind_optional_supabase_identity,
_require_ops_admin,
build_health_payload,
build_system_status_payload,
_require_supabase_identity,
@@ -225,6 +227,43 @@ async def auth_me(request: Request):
}
@router.get("/api/ops/users")
async def ops_search_users(request: Request, q: str = "", limit: int = 20):
_assert_entitlement(request)
_require_ops_admin(request)
from src.database.db_manager import DBManager
db = DBManager()
users = db.search_users(q, limit=limit)
return {"users": users}
@router.get("/api/ops/leaderboard/weekly")
async def ops_weekly_leaderboard(request: Request, limit: int = 20):
_assert_entitlement(request)
_require_ops_admin(request)
from src.database.db_manager import DBManager
db = DBManager()
return {"leaderboard": db.get_weekly_leaderboard(limit=limit)}
@router.post("/api/ops/users/grant-points")
async def ops_grant_points(request: Request, body: GrantPointsRequest):
_assert_entitlement(request)
admin = _require_ops_admin(request)
from src.database.db_manager import DBManager
db = DBManager()
result = db.grant_points_by_supabase_email(body.email, body.points)
result["operator_email"] = admin.get("email")
if not result.get("ok"):
reason = str(result.get("reason") or "grant_points_failed")
status_code = 404 if reason == "user_not_found" else 400
raise HTTPException(status_code=status_code, detail=result)
return result
@router.get("/api/payments/config")
async def payment_config(request: Request):
_assert_entitlement(request)