From 13cd1764492578177440d1798861c72960e0d7e8 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 21 Mar 2026 13:10:27 +0800 Subject: [PATCH] Backfill ops memberships with Supabase auth user data --- src/auth/supabase_entitlement.py | 43 ++++++++++++++++++++++++++++++++ web/routes.py | 19 ++++++++++---- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/auth/supabase_entitlement.py b/src/auth/supabase_entitlement.py index 7b4463c1..a987d4de 100644 --- a/src/auth/supabase_entitlement.py +++ b/src/auth/supabase_entitlement.py @@ -101,6 +101,9 @@ class SupabaseEntitlementService: "Accept": "application/json", } + def _admin_user_endpoint(self, user_id: str) -> str: + return f"{self.supabase_url}/auth/v1/admin/users/{user_id}" + def get_identity(self, access_token: str) -> Optional[SupabaseIdentity]: if not access_token: return None @@ -265,5 +268,45 @@ class SupabaseEntitlementService: logger.warning(f"supabase active subscriptions query error: {exc}") return [] + def get_auth_users(self, user_ids: List[str]) -> Dict[str, Dict[str, object]]: + if not self.service_role_key: + logger.warning("SUPABASE_SERVICE_ROLE_KEY is missing") + return {} + + keys = [] + for item in user_ids or []: + key = str(item or "").strip().lower() + if key and key not in keys: + keys.append(key) + if not keys: + return {} + + out: Dict[str, Dict[str, object]] = {} + for user_id in keys: + try: + response = requests.get( + self._admin_user_endpoint(user_id), + headers=self._request_headers_for_service_role(), + timeout=self.timeout_sec, + ) + if response.status_code != 200: + logger.warning( + "supabase admin user query failed user_id={} status={}", + user_id, + response.status_code, + ) + continue + raw = response.json() if response.content else {} + payload = raw.get("user") if isinstance(raw, dict) and isinstance(raw.get("user"), dict) else raw + if not isinstance(payload, dict): + continue + out[user_id] = { + "email": str(payload.get("email") or "").strip(), + "created_at": payload.get("created_at"), + } + except Exception as exc: + logger.warning(f"supabase admin user query error user_id={user_id}: {exc}") + return out + SUPABASE_ENTITLEMENT = SupabaseEntitlementService() diff --git a/web/routes.py b/web/routes.py index df7b1f28..620d2a23 100644 --- a/web/routes.py +++ b/web/routes.py @@ -256,19 +256,28 @@ async def ops_memberships(request: Request, limit: int = 200): db = DBManager() subscriptions = SUPABASE_ENTITLEMENT.list_active_subscriptions(limit=limit) - user_map = db.get_users_by_supabase_user_ids( - [str(item.get("user_id") or "") for item in subscriptions] - ) + subscription_user_ids = [str(item.get("user_id") or "") for item in subscriptions] + user_map = db.get_users_by_supabase_user_ids(subscription_user_ids) + unresolved_user_ids = [ + user_id + for user_id in subscription_user_ids + if str(user_id or "").strip().lower() + and not str( + (user_map.get(str(user_id).strip().lower(), {}) or {}).get("supabase_email") or "" + ).strip() + ] + auth_user_map = SUPABASE_ENTITLEMENT.get_auth_users(unresolved_user_ids) deduped: dict[str, dict] = {} for item in subscriptions: user_id = str(item.get("user_id") or "").strip().lower() local_user = user_map.get(user_id, {}) + auth_user = auth_user_map.get(user_id, {}) row = { "user_id": user_id, - "email": str(local_user.get("supabase_email") or ""), + "email": str(local_user.get("supabase_email") or auth_user.get("email") or ""), "telegram_id": local_user.get("telegram_id"), "username": local_user.get("username"), - "registered_at": local_user.get("created_at"), + "registered_at": local_user.get("created_at") or auth_user.get("created_at"), "plan_code": item.get("plan_code"), "starts_at": item.get("starts_at"), "expires_at": item.get("expires_at"),