Release v1.5.0 and add ops admin dashboard
This commit is contained in:
+23
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user