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

@@ -370,16 +370,86 @@ async def _apply_metadata_and_attach(
except Exception as exc: # noqa: BLE001
status["warnings"].append(f"featured_set_failed (product): {exc}")
else:
try:
await wp_set_featured_media(client, attach_to_post, media["id"])
# Issue #90 — set_featured used to hit /wp/v2/posts/{id}
# only, which 404s for pages and other CPTs. Now: try
# `posts` first (the common case), and on miss, walk the
# thumbnail-supporting rest_bases (pages, then any other
# CPT that opted in via add_theme_support('post-thumbnails')
# / declares ``thumbnail`` in its supports list).
ctx = await _set_featured_with_fallback(client, attach_to_post, media["id"])
if ctx is not None:
status["featured_set"] = True
status["featured_context"] = "post"
except Exception as exc: # noqa: BLE001
status["warnings"].append(f"featured_set_failed (post): {exc}")
status["featured_context"] = ctx
else:
status["warnings"].append(
f"featured_set_failed: post {attach_to_post} not found "
"in any thumbnail-supporting post type"
)
return status
async def _set_featured_with_fallback(
client: WordPressClient,
target_id: int,
media_id: int,
) -> str | None:
"""Set featured image on ``target_id`` across post / page / CPT.
Tries ``posts`` first (the 99% case — and the path covered by
``wp_set_featured_media`` to keep existing test seams stable),
then falls back to ``pages`` and any other rest_base that declares
``thumbnail`` support via ``/wp/v2/types``. Returns the rest_base on
success, ``None`` when nothing accepted the write.
"""
try:
await wp_set_featured_media(client, target_id, media_id)
return "post"
except Exception:
pass
body = {"featured_media": media_id}
try:
await client.post(f"pages/{target_id}", json_data=body)
return "page"
except Exception:
pass
try:
types_data = await client.get("types")
except Exception:
return None
if not isinstance(types_data, dict):
return None
for slug, data in types_data.items():
if not isinstance(data, dict):
continue
rest_base = data.get("rest_base") or slug
if rest_base in ("posts", "pages", "media", "attachment"):
continue
supports = data.get("supports") or {}
# `supports` is sometimes a list (older WP / some plugins) and
# sometimes a dict keyed by feature. Treat both as truthy when
# ``thumbnail`` is present.
if isinstance(supports, dict):
if not supports.get("thumbnail"):
continue
elif isinstance(supports, list):
if "thumbnail" not in supports:
continue
else:
continue
try:
await client.post(f"{rest_base}/{target_id}", json_data=body)
return rest_base
except Exception:
continue
return None
def _format_upload_result(media: dict[str, Any], *, source: str) -> dict[str, Any]:
title = media.get("title")
rendered_title = title.get("rendered") if isinstance(title, dict) else title