Renecto commited on
Commit
00c6951
·
verified ·
1 Parent(s): b1b13d2

feat: add /reset-password page

Browse files
Files changed (2) hide show
  1. app.py +19 -9
  2. supabase_logger.py +133 -123
app.py CHANGED
@@ -96,22 +96,31 @@ _user_profile_cache: dict = {}
96
  # 最後に解決された source を保持(Gradio WebSocket スレッドは ContextVar が伝播しないため)
97
  _last_known_source: dict = {}
98
 
 
 
 
 
 
99
  # --- Request Logging Middleware ---
100
- def _resolve_source(request: Request) -> dict:
101
- """リクエストヘッダから流入元を判定して source_channel / source_domain を返す。
 
102
  判定優先順:
103
- 1. Referer に huggingface.co/spaces/ を含む hf_embedHF UI 経由の埋め込み表示
104
- 2. ホストが *.hf.spacehf_space(Space への直接アクセス)
105
- 3. それ以外unknown
 
106
  """
107
  headers = request.headers
108
  referer = (headers.get("referer") or "").lower()
109
  host = (headers.get("x-forwarded-host") or headers.get("host") or "").lower()
 
 
110
  if "huggingface.co/spaces/" in referer:
111
- return {"source_channel": "hf_embed", "source_domain": "huggingface.co"}
112
  if host.endswith(".hf.space"):
113
- return {"source_channel": "hf_space", "source_domain": host}
114
- return {"source_channel": "unknown", "source_domain": host or None}
115
 
116
 
117
  class RequestLoggingMiddleware(BaseHTTPMiddleware):
@@ -128,7 +137,8 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
128
  # Resolve source (flow-in channel) and store in contextvars
129
  source_info = _resolve_source(request)
130
  set_request_source(source_info)
131
- _last_known_source.update(source_info)
 
132
 
133
  # print(f"[REQUEST] method={method} path={path}{user_tag}")
134
  # log_event("request", f"{method} {path}", metadata={"method": method, "path": path})
 
96
  # 最後に解決された source を保持(Gradio WebSocket スレッドは ContextVar が伝播しないため)
97
  _last_known_source: dict = {}
98
 
99
+ # 内部プロキシホスト(ログ対象外)
100
+ _INTERNAL_PROXY_DOMAINS = {
101
+ "proxy.spaces.internal.huggingface.tech",
102
+ }
103
+
104
  # --- Request Logging Middleware ---
105
+ def _resolve_source(request: Request) -> dict | None:
106
+ """リクエストヘッダから流入元を判定して source_domain を返す。
107
+ 内部プロキシホストの場合は None を返してログを除外する。
108
  判定優先順:
109
+ 1. ホストが内部プロキシNoneログ除外
110
+ 2. Referer に huggingface.co/spaces/ を含む source_domain="huggingface.co"
111
+ 3. ホストが *.hf.space source_domain=host
112
+ 4. それ以外 → source_domain=host or None
113
  """
114
  headers = request.headers
115
  referer = (headers.get("referer") or "").lower()
116
  host = (headers.get("x-forwarded-host") or headers.get("host") or "").lower()
117
+ if host in _INTERNAL_PROXY_DOMAINS:
118
+ return None
119
  if "huggingface.co/spaces/" in referer:
120
+ return {"source_domain": "huggingface.co"}
121
  if host.endswith(".hf.space"):
122
+ return {"source_domain": host}
123
+ return {"source_domain": host or None}
124
 
125
 
126
  class RequestLoggingMiddleware(BaseHTTPMiddleware):
 
137
  # Resolve source (flow-in channel) and store in contextvars
138
  source_info = _resolve_source(request)
139
  set_request_source(source_info)
140
+ if source_info is not None:
141
+ _last_known_source.update(source_info)
142
 
143
  # print(f"[REQUEST] method={method} path={path}{user_tag}")
144
  # log_event("request", f"{method} {path}", metadata={"method": method, "path": path})
supabase_logger.py CHANGED
@@ -1,123 +1,133 @@
1
- """
2
- Supabase structured logger with per-request user context.
3
-
4
- Usage:
5
- from supabase_logger import init_logger, log_event, set_user_context, get_user_context, set_request_source
6
-
7
- # At startup
8
- init_logger(supabase_client)
9
-
10
- # In middleware / auth
11
- set_user_context({"user_id": "...", "email": "..."})
12
- set_request_source({"source_channel": "hf_space", "source_domain": "dlpo-mbok-dev.hf.space"})
13
-
14
- # Anywhere
15
- log_event("user", "click_step01", session_id="abc123", metadata={"force": False})
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- import threading
21
- import traceback
22
- from contextvars import ContextVar
23
- from datetime import datetime, timezone
24
- from typing import Any, Dict, Optional
25
-
26
- from supabase import Client
27
-
28
- _supabase: Optional[Client] = None
29
-
30
- _current_user: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
31
- "current_user", default=None
32
- )
33
- _request_source: ContextVar[Optional[Dict[str, str]]] = ContextVar(
34
- "request_source", default=None
35
- )
36
-
37
-
38
- def init_logger(client: Client) -> None:
39
- global _supabase
40
- _supabase = client
41
-
42
-
43
- def set_user_context(user: Optional[Dict[str, Any]]) -> None:
44
- _current_user.set(user)
45
-
46
-
47
- def get_user_context() -> Optional[Dict[str, Any]]:
48
- return _current_user.get()
49
-
50
-
51
- def set_request_source(source: Optional[Dict[str, str]]) -> None:
52
- """リクエストの流入元情報をセット(FastAPI middleware から呼ぶ)"""
53
- _request_source.set(source)
54
-
55
-
56
- def log_event(
57
- event_type: str,
58
- message: str,
59
- *,
60
- level: str = "INFO",
61
- metadata: Optional[Dict[str, Any]] = None,
62
- user_override: Optional[Dict[str, Any]] = None,
63
- session_id: Optional[str] = None,
64
- source: Optional[Dict[str, str]] = None,
65
- ) -> None:
66
- """
67
- Insert a structured log row into Supabase ``activity_logs``.
68
-
69
- Source resolution priority:
70
- 1. Explicit ``source`` argument
71
- 2. ContextVar set by middleware (``set_request_source``)
72
- 3. ``source_channel`` / ``source_domain`` keys inside ``metadata``
73
-
74
- Resolved values are stored in the dedicated columns; any ``source_*``
75
- keys are stripped from ``metadata`` to avoid duplication.
76
-
77
- Falls back to stdout if the insert fails so the main request is
78
- never blocked by a logging error.
79
- """
80
- user = user_override or _current_user.get()
81
-
82
- # --- source resolution ---
83
- clean_meta: Dict[str, Any] = dict(metadata) if metadata else {}
84
- resolved_source: Dict[str, Any] = {}
85
-
86
- if source:
87
- resolved_source = source
88
- elif _request_source.get():
89
- resolved_source = _request_source.get() # type: ignore[assignment]
90
- else:
91
- # Fall back to keys embedded in metadata
92
- ch = clean_meta.pop("source_channel", None)
93
- dm = clean_meta.pop("source_domain", None)
94
- if ch or dm:
95
- resolved_source = {"source_channel": ch, "source_domain": dm}
96
-
97
- # Strip source_* from metadata even when source came from arg/ContextVar
98
- clean_meta.pop("source_channel", None)
99
- clean_meta.pop("source_domain", None)
100
-
101
- row = {
102
- "event_type": event_type,
103
- "level": level,
104
- "message": message,
105
- "user_id": user.get("user_id") if user else None,
106
- "user_email": user.get("email") if user else None,
107
- "session_id": session_id,
108
- "source_channel": resolved_source.get("source_channel"),
109
- "source_domain": resolved_source.get("source_domain"),
110
- "metadata": clean_meta,
111
- "created_at": datetime.now(timezone.utc).isoformat(),
112
- }
113
-
114
- def _insert():
115
- try:
116
- if _supabase is not None:
117
- _supabase.table("activity_logs").insert(row).execute()
118
- except Exception:
119
- print(f"[SUPABASE_LOG_ERROR] insert failed: {traceback.format_exc()}")
120
- print(f"[SUPABASE_LOG_ERROR] row: {row}")
121
-
122
- # Fire-and-forget so the request is never delayed
123
- threading.Thread(target=_insert, daemon=True).start()
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Supabase structured logger with per-request user context.
3
+
4
+ Usage:
5
+ from supabase_logger import init_logger, log_event, set_user_context, get_user_context, set_request_source
6
+
7
+ # At startup
8
+ init_logger(supabase_client)
9
+
10
+ # In middleware / auth
11
+ set_user_context({"user_id": "...", "email": "..."})
12
+ set_request_source({"source_domain": "dlpo-mbok-dev.hf.space"})
13
+
14
+ # Anywhere
15
+ log_event("user", "click_step01", session_id="abc123", metadata={"force": False})
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import threading
21
+ import traceback
22
+ from contextvars import ContextVar
23
+ from datetime import datetime, timezone
24
+ from typing import Any, Dict, Optional
25
+
26
+ from supabase import Client
27
+
28
+ _supabase: Optional[Client] = None
29
+
30
+ _current_user: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
31
+ "current_user", default=None
32
+ )
33
+ _request_source: ContextVar[Optional[Dict[str, str]]] = ContextVar(
34
+ "request_source", default=None
35
+ )
36
+
37
+ # Domains whose events are never persisted (internal HF proxy health checks etc.)
38
+ _BLOCKED_SOURCE_DOMAINS = {
39
+ "proxy.spaces.internal.huggingface.tech",
40
+ }
41
+
42
+
43
+ def init_logger(client: Client) -> None:
44
+ global _supabase
45
+ _supabase = client
46
+
47
+
48
+ def set_user_context(user: Optional[Dict[str, Any]]) -> None:
49
+ _current_user.set(user)
50
+
51
+
52
+ def get_user_context() -> Optional[Dict[str, Any]]:
53
+ return _current_user.get()
54
+
55
+
56
+ def set_request_source(source: Optional[Dict[str, str]]) -> None:
57
+ """リクエストの流入元情報をセット(FastAPI middleware から呼ぶ)"""
58
+ _request_source.set(source)
59
+
60
+
61
+ def log_event(
62
+ event_type: str,
63
+ message: str,
64
+ *,
65
+ level: str = "INFO",
66
+ metadata: Optional[Dict[str, Any]] = None,
67
+ user_override: Optional[Dict[str, Any]] = None,
68
+ session_id: Optional[str] = None,
69
+ source: Optional[Dict[str, str]] = None,
70
+ ) -> None:
71
+ """
72
+ Insert a structured log row into Supabase ``activity_logs``.
73
+
74
+ Source resolution priority:
75
+ 1. Explicit ``source`` argument
76
+ 2. ContextVar set by middleware (``set_request_source``)
77
+ 3. ``source_domain`` key inside ``metadata``
78
+
79
+ If the resolved ``source_domain`` is an internal proxy domain the event is
80
+ silently dropped (not persisted).
81
+
82
+ Falls back to stdout if the insert fails so the main request is
83
+ never blocked by a logging error.
84
+ """
85
+ user = user_override or _current_user.get()
86
+
87
+ # --- source resolution ---
88
+ clean_meta: Dict[str, Any] = dict(metadata) if metadata else {}
89
+ resolved_source: Dict[str, Any] = {}
90
+
91
+ if source:
92
+ resolved_source = source
93
+ elif _request_source.get():
94
+ resolved_source = _request_source.get() # type: ignore[assignment]
95
+ else:
96
+ # Fall back to key embedded in metadata
97
+ dm = clean_meta.pop("source_domain", None)
98
+ if dm:
99
+ resolved_source = {"source_domain": dm}
100
+
101
+ # Strip source_domain from metadata to avoid duplication
102
+ clean_meta.pop("source_domain", None)
103
+ # Also strip legacy source_channel if still present in older metadata
104
+ clean_meta.pop("source_channel", None)
105
+
106
+ source_domain = resolved_source.get("source_domain")
107
+
108
+ # Drop events from internal proxy domains
109
+ if source_domain in _BLOCKED_SOURCE_DOMAINS:
110
+ return
111
+
112
+ row = {
113
+ "event_type": event_type,
114
+ "level": level,
115
+ "message": message,
116
+ "user_id": user.get("user_id") if user else None,
117
+ "user_email": user.get("email") if user else None,
118
+ "session_id": session_id,
119
+ "source_domain": source_domain,
120
+ "metadata": clean_meta,
121
+ "created_at": datetime.now(timezone.utc).isoformat(),
122
+ }
123
+
124
+ def _insert():
125
+ try:
126
+ if _supabase is not None:
127
+ _supabase.table("activity_logs").insert(row).execute()
128
+ except Exception:
129
+ print(f"[SUPABASE_LOG_ERROR] insert failed: {traceback.format_exc()}")
130
+ print(f"[SUPABASE_LOG_ERROR] row: {row}")
131
+
132
+ # Fire-and-forget so the request is never delayed
133
+ threading.Thread(target=_insert, daemon=True).start()