1616
1717logger = structlog .get_logger (__name__ )
1818
19- _PRO_STATE_CACHE : "OrderedDict[str, tuple[bool, float]]" = OrderedDict ()
20- _PRO_STATE_CACHE_LOCK = threading .Lock ()
21-
22-
23- def _pro_cache_ttl_seconds () -> int :
24- try :
25- configured_ttl = int (getattr (get_settings (), "pro_state_cache_ttl_seconds" , 30 ) or 30 )
26- except (ValueError , TypeError ):
27- configured_ttl = 30
28- return min (max (configured_ttl , 1 ), 30 )
29-
30-
31- def _pro_cache_max_entries () -> int :
32- try :
33- configured_max = int (getattr (get_settings (), "pro_state_cache_max_entries" , 1000 ) or 1000 )
34- except (ValueError , TypeError ):
35- configured_max = 1000
36- return min (max (configured_max , 1 ), 10000 )
37-
38-
39- def _prune_pro_cache_locked (now : float ) -> None :
40- expired_keys = [user_id for user_id , (_is_pro , expires_at ) in _PRO_STATE_CACHE .items () if expires_at <= now ]
41- for user_id in expired_keys :
42- _PRO_STATE_CACHE .pop (user_id , None )
43-
44- max_entries = _pro_cache_max_entries ()
45- while len (_PRO_STATE_CACHE ) > max_entries :
46- _PRO_STATE_CACHE .popitem (last = False )
47-
48-
49- def invalidate_pro_cache (user_id : str ) -> None :
50- if not user_id :
51- return
52- with _PRO_STATE_CACHE_LOCK :
53- _PRO_STATE_CACHE .pop (user_id , None )
54- async def _clear () -> None :
55- redis = await safe_redis_call (get_redis , operation = "connect" )
56- if redis is None :
57- return
58- await safe_redis_call (redis .delete , f"depthapi:user:is_pro:{ user_id } " , operation = "delete" )
59-
60- try :
61- asyncio .create_task (_clear ())
62- except Exception as exc :
63- logger .debug ("auth_invalidate_pro_cache_failed" , user_id_hash = anonymize_user_id (user_id ), error = str (exc ))
64-
65-
6619@lru_cache (maxsize = 1 )
6720def get_supabase () -> Client | None :
6821 settings = get_settings ()
@@ -82,51 +35,3 @@ def get_supabase_admin() -> Client | None:
8235 if hasattr (secret_key , "get_secret_value" ):
8336 secret_key = secret_key .get_secret_value ()
8437 return create_client (settings .supabase_url , secret_key ) # type: ignore
85-
86-
87- async def check_is_pro (user_id : str , force_refresh : bool = False ) -> bool :
88- """Check if a user has pro status in the database (deprecated for v1)."""
89- if not user_id :
90- return False
91-
92- now = time .time ()
93- if not force_refresh :
94- with _PRO_STATE_CACHE_LOCK :
95- _prune_pro_cache_locked (now )
96- cached = _PRO_STATE_CACHE .get (user_id )
97- if cached and cached [1 ] > now :
98- _PRO_STATE_CACHE .move_to_end (user_id )
99- return cached [0 ]
100- try :
101- redis = await safe_redis_call (get_redis , operation = "connect" )
102- raw = await safe_redis_call (redis .get , f"depthapi:user:is_pro:{ user_id } " , operation = "get" ) if redis else None
103- if raw is not None :
104- value = raw .decode ("utf-8" ) if isinstance (raw , (bytes , bytearray )) else str (raw )
105- is_pro = value == "1"
106- with _PRO_STATE_CACHE_LOCK :
107- _prune_pro_cache_locked (now )
108- _PRO_STATE_CACHE [user_id ] = (is_pro , now + _pro_cache_ttl_seconds ())
109- _PRO_STATE_CACHE .move_to_end (user_id )
110- return is_pro
111- except Exception as exc :
112- logger .debug ("auth_pro_cache_redis_read_failed" , user_id_hash = anonymize_user_id (user_id ), error = str (exc ))
113-
114- supabase = get_supabase_admin ()
115- if not supabase :
116- return False
117-
118- try :
119- response = await asyncio .to_thread (
120- lambda : supabase .table ("users" ).select ("is_pro" ).eq ("id" , user_id ).single ().execute ()
121- )
122- data = getattr (response , "data" , None )
123- is_pro = bool (data .get ("is_pro" , False )) if isinstance (data , dict ) else False
124-
125- with _PRO_STATE_CACHE_LOCK :
126- _prune_pro_cache_locked (now )
127- _PRO_STATE_CACHE [user_id ] = (is_pro , now + _pro_cache_ttl_seconds ())
128- _PRO_STATE_CACHE .move_to_end (user_id )
129- return is_pro
130- except Exception as e :
131- logger .error ("auth_check_is_pro_failed" , user_id_hash = anonymize_user_id (user_id ), error = str (e ))
132- return False
0 commit comments