diff --git a/frontend/components/account/AccountCenter.tsx b/frontend/components/account/AccountCenter.tsx index 5a94b5e1..85903214 100644 --- a/frontend/components/account/AccountCenter.tsx +++ b/frontend/components/account/AccountCenter.tsx @@ -49,6 +49,8 @@ type AuthMeResponse = { user_id?: string | null; email?: string | null; points?: number; + weekly_points?: number; + weekly_rank?: number | string | null; entitlement_mode?: string | null; auth_required?: boolean; subscription_required?: boolean; @@ -126,7 +128,12 @@ type InfoRowProps = { isPrimary?: boolean; }; -const InfoRow = ({ icon: Icon, label, value, isPrimary = false }: InfoRowProps) => ( +const InfoRow = ({ + icon: Icon, + label, + value, + isPrimary = false, +}: InfoRowProps) => (
@@ -363,8 +370,12 @@ export function AccountCenter() { const pointsRaw = Number.isFinite(backendPointsRaw) ? Math.max(backendPointsRaw, metadataPointsSafe) : metadataPointsSafe; - const weeklyPointsRaw = Number(user?.user_metadata?.weekly_points ?? 0); - const weeklyRankRaw = user?.user_metadata?.weekly_rank; + const backendWeeklyPointsRaw = Number(backend?.weekly_points); + const metadataWeeklyPointsRaw = Number(user?.user_metadata?.weekly_points ?? 0); + const weeklyPointsRaw = Number.isFinite(backendWeeklyPointsRaw) + ? backendWeeklyPointsRaw + : metadataWeeklyPointsRaw; + const weeklyRankRaw = backend?.weekly_rank ?? user?.user_metadata?.weekly_rank; const totalPoints = Number.isFinite(pointsRaw) ? Math.max(0, pointsRaw) : 0; const weeklyPoints = Number.isFinite(weeklyPointsRaw) ? Math.max(0, weeklyPointsRaw) @@ -421,7 +432,8 @@ export function AccountCenter() { : 0; const discountUnits = Math.floor(actualRedeem / pointsPerUsdc); const pointsUsed = discountUnits * pointsPerUsdc; - const canRedeem = pointsEnabled && maxDiscountUsdc > 0 && totalPoints >= pointsPerUsdc; + const canRedeem = + pointsEnabled && maxDiscountUsdc > 0 && totalPoints >= pointsPerUsdc; const applyDiscount = usePoints && canRedeem && pointsUsed > 0; return { @@ -595,7 +607,8 @@ export function AccountCenter() { payment_mode: "strict", allowed_wallet: payingWallet, use_points: billing.canRedeem && usePoints, - points_to_consume: billing.canRedeem && usePoints ? billing.pointsUsed : 0, + points_to_consume: + billing.canRedeem && usePoints ? billing.pointsUsed : 0, metadata: { source: "account_center" }, }), }); @@ -749,9 +762,6 @@ export function AccountCenter() {

账户中心

-

- 积分体系 v4.3 · 管理身份与订阅计划 -

@@ -969,7 +979,9 @@ export function AccountCenter() { onPay={() => void handleOverlayCheckout()} onClose={() => setShowOverlay(false)} payBusy={paymentBusy} - payLabel={hasPayingWallet ? "立即订阅并激活服务" : "连接钱包并支付"} + payLabel={ + hasPayingWallet ? "立即订阅并激活服务" : "连接钱包并支付" + } errorText={paymentError || undefined} infoText={paymentInfo || undefined} faqHref="/account" @@ -990,7 +1002,7 @@ export function AccountCenter() { Telegram Bot 绑定

- 将下方命令发送给 Bot,实现全平台气象推送与权限同步。 + 将下方命令发送给到群聊,实现全平台气象推送与权限同步。

diff --git a/src/database/db_manager.py b/src/database/db_manager.py index 9c70a006..dff9cfd1 100644 --- a/src/database/db_manager.py +++ b/src/database/db_manager.py @@ -392,3 +392,43 @@ class DBManager: (week_key, limit), ) return [dict(row) for row in cursor.fetchall()] + + def get_weekly_profile_by_supabase_user_id(self, supabase_user_id: str) -> Dict[str, Any]: + key = str(supabase_user_id or "").strip().lower() + if not key: + return {"weekly_points": 0, "weekly_rank": None, "total_ranked": 0} + + now = datetime.now() + iso_year, iso_week, _ = now.isocalendar() + week_key = f"{iso_year}-W{iso_week:02d}" + with self._get_connection() as conn: + conn.row_factory = sqlite3.Row + rows = conn.execute( + """ + SELECT + telegram_id, + lower(trim(COALESCE(supabase_user_id, ''))) AS supabase_key, + COALESCE(points, 0) AS points, + COALESCE(message_count, 0) AS message_count, + CASE + WHEN weekly_points_week = ? THEN COALESCE(weekly_points, 0) + ELSE 0 + END AS weekly_points + FROM users + ORDER BY weekly_points DESC, points DESC, message_count DESC, telegram_id ASC + """, + (week_key,), + ).fetchall() + + weekly_rank: Optional[int] = None + weekly_points = 0 + for idx, row in enumerate(rows, start=1): + if str(row["supabase_key"] or "") == key: + weekly_rank = idx + weekly_points = int(row["weekly_points"] or 0) + break + return { + "weekly_points": max(0, int(weekly_points or 0)), + "weekly_rank": weekly_rank, + "total_ranked": len(rows), + } diff --git a/web/app.py b/web/app.py index c7a9028e..f61d2004 100644 --- a/web/app.py +++ b/web/app.py @@ -144,6 +144,21 @@ def _resolve_auth_points(request: Request) -> int: return points +def _resolve_weekly_profile(request: Request) -> Dict[str, Any]: + user_id = str(getattr(request.state, "auth_user_id", "") or "").strip() + if not user_id: + return {"weekly_points": 0, "weekly_rank": None} + try: + profile = _account_db.get_weekly_profile_by_supabase_user_id(user_id) + return { + "weekly_points": int(profile.get("weekly_points") or 0), + "weekly_rank": profile.get("weekly_rank"), + } + except Exception as exc: + logger.warning(f"auth weekly profile fallback failed user_id={user_id}: {exc}") + return {"weekly_points": 0, "weekly_rank": None} + + def _assert_entitlement(request: Request) -> None: if SUPABASE_ENTITLEMENT.enabled: if _legacy_service_token_valid(request): @@ -1117,12 +1132,15 @@ async def auth_me(request: Request): subscription_active = None points = _resolve_auth_points(request) + weekly_profile = _resolve_weekly_profile(request) return { "authenticated": bool(user_id), "user_id": user_id, "email": getattr(request.state, "auth_email", None), "points": points, + "weekly_points": weekly_profile["weekly_points"], + "weekly_rank": weekly_profile["weekly_rank"], "entitlement_mode": ( "supabase_required" if SUPABASE_ENTITLEMENT.enabled and _SUPABASE_AUTH_REQUIRED