feat(v3.13.0): settings live DB reads, remove Directus/Appwrite, admin stats
Settings fixes: - MAX_SITES_PER_USER, USER_RATE_LIMIT_PER_MIN/HR now read from DB settings table (DB > ENV > default), so dashboard/settings changes apply without restart. Sync cache refreshed on every save or delete. - /api/me reports the live DB value for max_sites_per_user. Admin improvements: - Admin users bypass per-user rate limiting entirely (role=admin or ADMIN_EMAILS). - Admin Overview now shows platform stats: registered users, new users (7d), total user sites, available tools. Plugin cleanup: - Appwrite and Directus plugins removed from the active registry (8 plugins now: WordPress, WooCommerce, WordPress Specialist, Gitea, n8n, Supabase, OpenPanel, Coolify). Plugin code is retained for future re-enabling. - Settings page plugin visibility list updated to match. Mobile onboarding: - Stepper steps on narrow viewports stack vertically with correct full border and rounded corners on each step. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,10 @@
|
||||
Usage:
|
||||
from core.settings import get_setting
|
||||
|
||||
enabled = await get_setting("ENABLED_PLUGINS", "wordpress,woocommerce,supabase,openpanel,gitea")
|
||||
enabled = await get_setting(
|
||||
"ENABLED_PLUGINS",
|
||||
"wordpress,woocommerce,wordpress_specialist,supabase,openpanel,gitea,n8n,coolify",
|
||||
)
|
||||
max_sites = int(await get_setting("MAX_SITES_PER_USER", "10"))
|
||||
"""
|
||||
|
||||
@@ -15,9 +18,14 @@ logger = logging.getLogger(__name__)
|
||||
# Cached plugin set for sync access (updated when settings change)
|
||||
_cached_plugins: set[str] | None = None
|
||||
|
||||
# Cached int settings for sync access (updated when settings change)
|
||||
_cached_max_sites: int | None = None
|
||||
_cached_rate_per_min: int | None = None
|
||||
_cached_rate_per_hr: int | None = None
|
||||
|
||||
# Default values for all managed settings
|
||||
SETTING_DEFAULTS: dict[str, str] = {
|
||||
"ENABLED_PLUGINS": "wordpress,woocommerce,supabase,openpanel,gitea",
|
||||
"ENABLED_PLUGINS": "wordpress,woocommerce,wordpress_specialist,supabase,openpanel,gitea,n8n,coolify",
|
||||
"MAX_SITES_PER_USER": "10",
|
||||
"USER_RATE_LIMIT_PER_MIN": "30",
|
||||
"USER_RATE_LIMIT_PER_HR": "500",
|
||||
@@ -84,15 +92,77 @@ async def get_setting(key: str, default: str | None = None) -> str | None:
|
||||
return SETTING_DEFAULTS.get(key)
|
||||
|
||||
|
||||
def get_cached_max_sites() -> int:
|
||||
"""Return the cached MAX_SITES_PER_USER value (sync, uses last refresh)."""
|
||||
if _cached_max_sites is not None:
|
||||
return _cached_max_sites
|
||||
env_val = os.environ.get("MAX_SITES_PER_USER")
|
||||
if env_val:
|
||||
try:
|
||||
return max(1, int(env_val))
|
||||
except ValueError:
|
||||
pass
|
||||
return int(SETTING_DEFAULTS["MAX_SITES_PER_USER"])
|
||||
|
||||
|
||||
def get_cached_rate_per_min() -> int:
|
||||
"""Return the cached USER_RATE_LIMIT_PER_MIN value (sync, uses last refresh)."""
|
||||
if _cached_rate_per_min is not None:
|
||||
return _cached_rate_per_min
|
||||
env_val = os.environ.get("USER_RATE_LIMIT_PER_MIN")
|
||||
if env_val:
|
||||
try:
|
||||
return max(1, int(env_val))
|
||||
except ValueError:
|
||||
pass
|
||||
return int(SETTING_DEFAULTS["USER_RATE_LIMIT_PER_MIN"])
|
||||
|
||||
|
||||
def get_cached_rate_per_hr() -> int:
|
||||
"""Return the cached USER_RATE_LIMIT_PER_HR value (sync, uses last refresh)."""
|
||||
if _cached_rate_per_hr is not None:
|
||||
return _cached_rate_per_hr
|
||||
env_val = os.environ.get("USER_RATE_LIMIT_PER_HR")
|
||||
if env_val:
|
||||
try:
|
||||
return max(1, int(env_val))
|
||||
except ValueError:
|
||||
pass
|
||||
return int(SETTING_DEFAULTS["USER_RATE_LIMIT_PER_HR"])
|
||||
|
||||
|
||||
async def refresh_plugin_cache() -> None:
|
||||
"""Refresh the cached plugin set from DB/ENV/default."""
|
||||
global _cached_plugins
|
||||
"""Refresh all sync-readable setting caches from DB/ENV/default."""
|
||||
global _cached_plugins, _cached_max_sites, _cached_rate_per_min, _cached_rate_per_hr
|
||||
|
||||
val = await get_setting("ENABLED_PLUGINS")
|
||||
if val:
|
||||
_cached_plugins = {p.strip().lower() for p in val.split(",") if p.strip()}
|
||||
else:
|
||||
_cached_plugins = None
|
||||
|
||||
try:
|
||||
raw = await get_setting("MAX_SITES_PER_USER")
|
||||
_cached_max_sites = max(1, int(raw)) if raw else int(SETTING_DEFAULTS["MAX_SITES_PER_USER"])
|
||||
except (ValueError, TypeError):
|
||||
_cached_max_sites = int(SETTING_DEFAULTS["MAX_SITES_PER_USER"])
|
||||
|
||||
try:
|
||||
raw = await get_setting("USER_RATE_LIMIT_PER_MIN")
|
||||
_cached_rate_per_min = (
|
||||
max(1, int(raw)) if raw else int(SETTING_DEFAULTS["USER_RATE_LIMIT_PER_MIN"])
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
_cached_rate_per_min = int(SETTING_DEFAULTS["USER_RATE_LIMIT_PER_MIN"])
|
||||
|
||||
try:
|
||||
raw = await get_setting("USER_RATE_LIMIT_PER_HR")
|
||||
_cached_rate_per_hr = (
|
||||
max(1, int(raw)) if raw else int(SETTING_DEFAULTS["USER_RATE_LIMIT_PER_HR"])
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
_cached_rate_per_hr = int(SETTING_DEFAULTS["USER_RATE_LIMIT_PER_HR"])
|
||||
|
||||
|
||||
async def save_setting(key: str, value: str) -> None:
|
||||
"""Save a setting to database and refresh caches."""
|
||||
@@ -101,8 +171,7 @@ async def save_setting(key: str, value: str) -> None:
|
||||
db = get_database()
|
||||
await db.set_setting(key, value)
|
||||
|
||||
if key == "ENABLED_PLUGINS":
|
||||
await refresh_plugin_cache()
|
||||
await refresh_plugin_cache()
|
||||
|
||||
|
||||
async def delete_setting_value(key: str) -> bool:
|
||||
@@ -112,8 +181,7 @@ async def delete_setting_value(key: str) -> bool:
|
||||
db = get_database()
|
||||
deleted = await db.delete_setting(key)
|
||||
|
||||
if key == "ENABLED_PLUGINS":
|
||||
await refresh_plugin_cache()
|
||||
await refresh_plugin_cache()
|
||||
|
||||
return deleted
|
||||
|
||||
|
||||
Reference in New Issue
Block a user