- Fix Python formatting (sync no longer strips blank lines from .py files) - Remove test_community_build.py (tests private sync module) - Fix ruff warnings in test files - Add CODE_OF_CONDUCT.md - Add .github/PULL_REQUEST_TEMPLATE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
309 lines
10 KiB
Python
309 lines
10 KiB
Python
"""
|
|
System Handler - manages Appwrite health checks and system utilities
|
|
|
|
Phase I.1: 8 tools
|
|
- Health: 6 (health_check, health_db, health_cache, health_storage, health_queue, health_time)
|
|
- Avatars: 2 (get_avatar_initials, get_qr_code)
|
|
"""
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from plugins.appwrite.client import AppwriteClient
|
|
|
|
|
|
def get_tool_specifications() -> list[dict[str, Any]]:
|
|
"""Return tool specifications for ToolGenerator (8 tools)"""
|
|
return [
|
|
# =====================
|
|
# HEALTH (6)
|
|
# =====================
|
|
{
|
|
"name": "health_check",
|
|
"method_name": "health_check",
|
|
"description": "Comprehensive health check of all Appwrite services. Returns status of database, cache, storage, and other services.",
|
|
"schema": {"type": "object", "properties": {}, "required": []},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "health_db",
|
|
"method_name": "health_db",
|
|
"description": "Check database health status. Returns ping time and connection status.",
|
|
"schema": {"type": "object", "properties": {}, "required": []},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "health_cache",
|
|
"method_name": "health_cache",
|
|
"description": "Check cache (Redis) health status.",
|
|
"schema": {"type": "object", "properties": {}, "required": []},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "health_storage",
|
|
"method_name": "health_storage",
|
|
"description": "Check storage health status. Verifies local and S3 storage availability.",
|
|
"schema": {"type": "object", "properties": {}, "required": []},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "health_queue",
|
|
"method_name": "health_queue",
|
|
"description": "Check queue health status. Returns queue sizes and processing status.",
|
|
"schema": {"type": "object", "properties": {}, "required": []},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "health_time",
|
|
"method_name": "health_time",
|
|
"description": "Check time synchronization status. Important for security and token validation.",
|
|
"schema": {"type": "object", "properties": {}, "required": []},
|
|
"scope": "read",
|
|
},
|
|
# =====================
|
|
# AVATARS (2)
|
|
# =====================
|
|
{
|
|
"name": "get_avatar_initials",
|
|
"method_name": "get_avatar_initials",
|
|
"description": "Generate an avatar image from name initials. Returns base64 encoded image.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Full name to generate initials from",
|
|
},
|
|
"width": {
|
|
"type": "integer",
|
|
"description": "Image width in pixels",
|
|
"default": 100,
|
|
"minimum": 1,
|
|
"maximum": 2000,
|
|
},
|
|
"height": {
|
|
"type": "integer",
|
|
"description": "Image height in pixels",
|
|
"default": 100,
|
|
"minimum": 1,
|
|
"maximum": 2000,
|
|
},
|
|
"background": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Background color in hex (without #)",
|
|
},
|
|
},
|
|
"required": [],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "get_qr_code",
|
|
"method_name": "get_qr_code",
|
|
"description": "Generate a QR code image for any text or URL. Returns base64 encoded PNG.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"text": {"type": "string", "description": "Text or URL to encode in QR code"},
|
|
"size": {
|
|
"type": "integer",
|
|
"description": "QR code size in pixels (width = height)",
|
|
"default": 400,
|
|
"minimum": 100,
|
|
"maximum": 1000,
|
|
},
|
|
"margin": {
|
|
"type": "integer",
|
|
"description": "Margin around QR code in modules",
|
|
"default": 1,
|
|
"minimum": 0,
|
|
"maximum": 10,
|
|
},
|
|
},
|
|
"required": ["text"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
]
|
|
|
|
|
|
# =====================
|
|
# HANDLER FUNCTIONS
|
|
# =====================
|
|
|
|
|
|
async def health_check(client: AppwriteClient) -> str:
|
|
"""Comprehensive health check of all services."""
|
|
try:
|
|
result = await client.health_check()
|
|
|
|
response = {
|
|
"success": True,
|
|
"healthy": result.get("healthy", False),
|
|
"services": result.get("services", {}),
|
|
"message": (
|
|
"All services operational" if result.get("healthy") else "Some services have issues"
|
|
),
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "healthy": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def health_db(client: AppwriteClient) -> str:
|
|
"""Check database health."""
|
|
try:
|
|
result = await client.health_db()
|
|
|
|
response = {
|
|
"success": True,
|
|
"service": "database",
|
|
"status": result.get("status", "unknown"),
|
|
"ping": result.get("ping"),
|
|
"message": (
|
|
"Database is healthy" if result.get("status") == "pass" else "Database has issues"
|
|
),
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"success": False, "service": "database", "status": "error", "error": str(e)}, indent=2
|
|
)
|
|
|
|
|
|
async def health_cache(client: AppwriteClient) -> str:
|
|
"""Check cache health."""
|
|
try:
|
|
result = await client.health_cache()
|
|
|
|
response = {
|
|
"success": True,
|
|
"service": "cache",
|
|
"status": result.get("status", "unknown"),
|
|
"ping": result.get("ping"),
|
|
"message": "Cache is healthy" if result.get("status") == "pass" else "Cache has issues",
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"success": False, "service": "cache", "status": "error", "error": str(e)}, indent=2
|
|
)
|
|
|
|
|
|
async def health_storage(client: AppwriteClient) -> str:
|
|
"""Check storage health."""
|
|
try:
|
|
result = await client.health_storage_local()
|
|
|
|
response = {
|
|
"success": True,
|
|
"service": "storage",
|
|
"status": result.get("status", "unknown"),
|
|
"ping": result.get("ping"),
|
|
"message": (
|
|
"Storage is healthy" if result.get("status") == "pass" else "Storage has issues"
|
|
),
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"success": False, "service": "storage", "status": "error", "error": str(e)}, indent=2
|
|
)
|
|
|
|
|
|
async def health_queue(client: AppwriteClient) -> str:
|
|
"""Check queue health."""
|
|
try:
|
|
result = await client.health_queue()
|
|
|
|
response = {
|
|
"success": True,
|
|
"service": "queue",
|
|
"status": result.get("status", "unknown"),
|
|
"size": result.get("size"),
|
|
"message": "Queue is healthy" if result.get("status") == "pass" else "Queue has issues",
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"success": False, "service": "queue", "status": "error", "error": str(e)}, indent=2
|
|
)
|
|
|
|
|
|
async def health_time(client: AppwriteClient) -> str:
|
|
"""Check time synchronization."""
|
|
try:
|
|
result = await client.health_time()
|
|
|
|
response = {
|
|
"success": True,
|
|
"service": "time",
|
|
"local_time": result.get("localTime"),
|
|
"remote_time": result.get("remoteTime"),
|
|
"diff": result.get("diff"),
|
|
"message": (
|
|
"Time is synchronized"
|
|
if abs(result.get("diff", 999)) < 60
|
|
else "Time drift detected"
|
|
),
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps(
|
|
{"success": False, "service": "time", "status": "error", "error": str(e)}, indent=2
|
|
)
|
|
|
|
|
|
async def get_avatar_initials(
|
|
client: AppwriteClient,
|
|
name: str | None = None,
|
|
width: int = 100,
|
|
height: int = 100,
|
|
background: str | None = None,
|
|
) -> str:
|
|
"""Generate avatar from initials."""
|
|
try:
|
|
result = await client.get_avatar_initials(
|
|
name=name, width=width, height=height, background=background
|
|
)
|
|
|
|
response = {
|
|
"success": True,
|
|
"message": f"Avatar generated for '{name or 'Anonymous'}'",
|
|
"width": width,
|
|
"height": height,
|
|
"content_type": result.get("content_type", "image/png"),
|
|
"size": result.get("size"),
|
|
"data_base64": result.get("data"),
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_qr_code(client: AppwriteClient, text: str, size: int = 400, margin: int = 1) -> str:
|
|
"""Generate QR code."""
|
|
try:
|
|
result = await client.get_qr_code(text=text, size=size, margin=margin)
|
|
|
|
response = {
|
|
"success": True,
|
|
"message": "QR code generated successfully",
|
|
"text": text,
|
|
"size": size,
|
|
"content_type": result.get("content_type", "image/png"),
|
|
"data_size": result.get("size"),
|
|
"data_base64": result.get("data"),
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|