# Conflicts: # backend/bot/app/web/admin_api_impl/settings.py # backend/bot/app/web/webapp/cache_helpers.py # frontend/src/admin/sections/SettingsSection.svelte # tests/test_admin_settings_manifest_i18n.py
145 lines
4.3 KiB
Python
145 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Awaitable, Callable, Optional
|
|
|
|
from bot.infra.redis import cache_delete, cache_delete_pattern, redis_key
|
|
from bot.utils.ttl_cache import AsyncTTLCache
|
|
from config.settings import Settings
|
|
|
|
_WEBAPP_USER_PAYLOAD_CACHES: dict[tuple[int, str, int], AsyncTTLCache] = {}
|
|
|
|
|
|
def reset_webapp_settings_cache(app: Any) -> None:
|
|
cache = app.get("webapp_settings_cache") if hasattr(app, "get") else None
|
|
if isinstance(cache, dict):
|
|
cache["ts"] = 0.0
|
|
cache["data"] = {}
|
|
|
|
|
|
def reset_subscription_guides_cache(app: Any) -> None:
|
|
cache = app.get("subscription_guides_config_cache") if hasattr(app, "get") else None
|
|
if isinstance(cache, dict):
|
|
cache["fingerprint"] = None
|
|
cache["status"] = None
|
|
|
|
|
|
def _payload_namespaces(include_devices: bool = False) -> tuple[str, ...]:
|
|
return ("me", "devices") if include_devices else ("me",)
|
|
|
|
|
|
def _webapp_user_payload_cache(
|
|
settings: Settings,
|
|
namespace: str,
|
|
ttl_seconds: int,
|
|
) -> Optional[AsyncTTLCache]:
|
|
ttl = max(0, int(ttl_seconds or 0))
|
|
if ttl <= 0:
|
|
return None
|
|
cache_key = (id(settings), namespace, ttl)
|
|
cache = _WEBAPP_USER_PAYLOAD_CACHES.get(cache_key)
|
|
if cache is None:
|
|
cache = AsyncTTLCache(
|
|
ttl_seconds=ttl,
|
|
settings=settings,
|
|
namespace=f"webapp:{namespace}",
|
|
)
|
|
_WEBAPP_USER_PAYLOAD_CACHES[cache_key] = cache
|
|
return cache
|
|
|
|
|
|
async def webapp_cached_user_payload(
|
|
settings: Settings,
|
|
namespace: str,
|
|
user_id: int,
|
|
ttl_seconds: int,
|
|
loader: Callable[[], Awaitable[Any]],
|
|
) -> Any:
|
|
cache = _webapp_user_payload_cache(settings, namespace, ttl_seconds)
|
|
if cache is None:
|
|
return await loader()
|
|
return await cache.get_or_load(str(int(user_id)), loader)
|
|
|
|
|
|
def invalidate_local_webapp_user_payload(
|
|
settings: Settings,
|
|
namespace: str,
|
|
user_id: int,
|
|
) -> None:
|
|
key = str(int(user_id))
|
|
for (settings_id, cache_namespace, _ttl), cache in tuple(
|
|
_WEBAPP_USER_PAYLOAD_CACHES.items()
|
|
):
|
|
if settings_id == id(settings) and cache_namespace == namespace:
|
|
cache.invalidate(key)
|
|
|
|
|
|
def invalidate_all_local_webapp_user_payloads(
|
|
settings: Settings,
|
|
namespace: Optional[str] = None,
|
|
*,
|
|
include_devices: Optional[bool] = None,
|
|
) -> None:
|
|
if include_devices is not None:
|
|
namespaces: Optional[set[str]] = set(_payload_namespaces(include_devices))
|
|
elif namespace is not None:
|
|
namespaces = {namespace}
|
|
else:
|
|
namespaces = None
|
|
|
|
for (settings_id, cache_namespace, _ttl), cache in tuple(
|
|
_WEBAPP_USER_PAYLOAD_CACHES.items()
|
|
):
|
|
if settings_id != id(settings):
|
|
continue
|
|
if namespaces is not None and cache_namespace not in namespaces:
|
|
continue
|
|
cache.invalidate()
|
|
|
|
|
|
async def invalidate_webapp_user_caches(
|
|
settings: Settings,
|
|
*user_ids: Optional[int],
|
|
include_devices: bool = False,
|
|
) -> None:
|
|
keys: list[str] = []
|
|
seen: set[int] = set()
|
|
for raw_user_id in user_ids:
|
|
if raw_user_id is None:
|
|
continue
|
|
try:
|
|
user_id = int(raw_user_id)
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if user_id in seen:
|
|
continue
|
|
seen.add(user_id)
|
|
keys.append(redis_key(settings, "cache", "webapp", "me", user_id))
|
|
invalidate_local_webapp_user_payload(settings, "me", user_id)
|
|
if include_devices:
|
|
keys.append(redis_key(settings, "cache", "webapp", "devices", user_id))
|
|
invalidate_local_webapp_user_payload(settings, "devices", user_id)
|
|
if keys:
|
|
await cache_delete(settings, *keys)
|
|
|
|
|
|
async def invalidate_all_webapp_user_payloads(
|
|
settings: Settings,
|
|
*,
|
|
include_devices: bool = False,
|
|
) -> None:
|
|
for namespace in _payload_namespaces(include_devices):
|
|
invalidate_all_local_webapp_user_payloads(settings, namespace=namespace)
|
|
try:
|
|
pattern = redis_key(settings, "cache", "webapp", namespace, "*")
|
|
await cache_delete_pattern(settings, pattern)
|
|
except Exception:
|
|
continue
|
|
|
|
|
|
async def invalidate_all_webapp_user_caches(
|
|
settings: Settings,
|
|
*,
|
|
include_devices: bool = False,
|
|
) -> None:
|
|
await invalidate_all_webapp_user_payloads(settings, include_devices=include_devices)
|