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:
2026-05-20 23:33:20 +02:00
parent f203ca88de
commit 43fd2201a0
223 changed files with 36183 additions and 4115 deletions

View File

@@ -0,0 +1,98 @@
"""Tests for ``resolve_public_base_url`` (2026-05-01).
Behaviour matrix that the helper has to cover so the dashboard's
``mcp_url`` rendering never falls back to a host-less path again — the
mcp-test app reproduced this by setting ``PUBLIC_URL=''`` (empty string,
not unset) in its env vars, which silently bypassed the previous
``os.environ.get("PUBLIC_URL", "http://localhost:8000")`` default.
"""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from core.dashboard.routes import resolve_public_base_url
def _request(headers: dict[str, str], scheme: str = "http", netloc: str = "localhost:8000"):
"""Build the smallest Request stub the helper needs."""
req = MagicMock()
req.headers = headers
req.url = MagicMock()
req.url.scheme = scheme
req.url.netloc = netloc
return req
def test_env_var_wins_when_set_and_non_empty(monkeypatch):
monkeypatch.setenv("PUBLIC_URL", "https://mcp.example.com")
req = _request({"host": "internal.fly.dev"}, scheme="http")
assert resolve_public_base_url(req) == "https://mcp.example.com"
def test_env_var_strips_trailing_slash(monkeypatch):
monkeypatch.setenv("PUBLIC_URL", "https://mcp.example.com/")
req = _request({})
assert resolve_public_base_url(req) == "https://mcp.example.com"
def test_empty_env_var_falls_back_to_request(monkeypatch):
"""The mcp-test bug — ``PUBLIC_URL=''`` no longer produces a host-less URL."""
monkeypatch.setenv("PUBLIC_URL", "")
req = _request({"x-forwarded-proto": "https", "x-forwarded-host": "mcp-test.example.com"})
assert resolve_public_base_url(req) == "https://mcp-test.example.com"
def test_whitespace_env_var_treated_as_unset(monkeypatch):
monkeypatch.setenv("PUBLIC_URL", " ")
req = _request({"x-forwarded-proto": "https", "x-forwarded-host": "wp.example.org"})
assert resolve_public_base_url(req) == "https://wp.example.org"
def test_unset_env_var_uses_forwarded_headers(monkeypatch):
monkeypatch.delenv("PUBLIC_URL", raising=False)
req = _request({"x-forwarded-proto": "https", "x-forwarded-host": "mcp.example.com"})
assert resolve_public_base_url(req) == "https://mcp.example.com"
def test_falls_back_to_host_header_when_no_xfh(monkeypatch):
monkeypatch.delenv("PUBLIC_URL", raising=False)
req = _request({"host": "mcp.local"}, scheme="https")
assert resolve_public_base_url(req) == "https://mcp.local"
def test_falls_back_to_request_url_when_no_headers(monkeypatch):
monkeypatch.delenv("PUBLIC_URL", raising=False)
req = _request({}, scheme="http", netloc="127.0.0.1:8001")
assert resolve_public_base_url(req) == "http://127.0.0.1:8001"
def test_handles_csv_x_forwarded_proto(monkeypatch):
"""Some upstream proxies append the original scheme: ``https, http``."""
monkeypatch.delenv("PUBLIC_URL", raising=False)
req = _request({"x-forwarded-proto": "https, http", "x-forwarded-host": "mcp.example.com"})
assert resolve_public_base_url(req) == "https://mcp.example.com"
def test_handles_csv_x_forwarded_host(monkeypatch):
monkeypatch.delenv("PUBLIC_URL", raising=False)
req = _request(
{"x-forwarded-proto": "https", "x-forwarded-host": "edge1.example.com, internal"}
)
assert resolve_public_base_url(req) == "https://edge1.example.com"
@pytest.mark.parametrize(
"url",
[
"https://mcp.example.com",
"https://mcp.example.com/",
"https://mcp.example.com//",
],
)
def test_no_trailing_slash_in_output(monkeypatch, url):
monkeypatch.setenv("PUBLIC_URL", url)
req = _request({})
assert resolve_public_base_url(req) == "https://mcp.example.com"