Initial commit: MCP Hub Community Edition v3.0.0
Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
6
plugins/appwrite/__init__.py
Normal file
6
plugins/appwrite/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Appwrite Plugin - Self-Hosted Backend-as-a-Service Management"""
|
||||
|
||||
from plugins.appwrite.client import AppwriteClient
|
||||
from plugins.appwrite.plugin import AppwritePlugin
|
||||
|
||||
__all__ = ["AppwritePlugin", "AppwriteClient"]
|
||||
1611
plugins/appwrite/client.py
Normal file
1611
plugins/appwrite/client.py
Normal file
File diff suppressed because it is too large
Load Diff
29
plugins/appwrite/handlers/__init__.py
Normal file
29
plugins/appwrite/handlers/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Appwrite Plugin Handlers"""
|
||||
|
||||
from plugins.appwrite.handlers import (
|
||||
databases,
|
||||
documents,
|
||||
# Phase I.4
|
||||
functions,
|
||||
messaging,
|
||||
# Phase I.3
|
||||
storage,
|
||||
system,
|
||||
teams,
|
||||
# Phase I.2
|
||||
users,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"databases",
|
||||
"documents",
|
||||
"system",
|
||||
# Phase I.2
|
||||
"users",
|
||||
"teams",
|
||||
# Phase I.3
|
||||
"storage",
|
||||
# Phase I.4
|
||||
"functions",
|
||||
"messaging",
|
||||
]
|
||||
809
plugins/appwrite/handlers/databases.py
Normal file
809
plugins/appwrite/handlers/databases.py
Normal file
@@ -0,0 +1,809 @@
|
||||
"""
|
||||
Databases Handler - manages Appwrite databases, collections, attributes, and indexes
|
||||
|
||||
Phase I.1: 18 tools
|
||||
- Databases: 5 (list, get, create, update, delete)
|
||||
- Collections: 5 (list, get, create, update, delete)
|
||||
- Attributes: 5 (list, create_string, create_integer, create_boolean, delete)
|
||||
- Indexes: 3 (list, create, delete)
|
||||
"""
|
||||
|
||||
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 (18 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# DATABASES (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_databases",
|
||||
"method_name": "list_databases",
|
||||
"description": "List all databases in the Appwrite project. Returns database ID, name, and creation date.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering (e.g., 'limit(25)', 'offset(0)')",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter databases by name",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_database",
|
||||
"method_name": "get_database",
|
||||
"description": "Get database details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"database_id": {"type": "string", "description": "Database ID"}},
|
||||
"required": ["database_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_database",
|
||||
"method_name": "create_database",
|
||||
"description": "Create a new database. Use 'unique()' as database_id to auto-generate a unique ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {
|
||||
"type": "string",
|
||||
"description": "Unique database ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"name": {"type": "string", "description": "Database name"},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable or disable the database",
|
||||
"default": True,
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_database",
|
||||
"method_name": "update_database",
|
||||
"description": "Update database name or enabled status.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"name": {"type": "string", "description": "New database name"},
|
||||
"enabled": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable or disable the database",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_database",
|
||||
"method_name": "delete_database",
|
||||
"description": "Delete a database and all its collections. This action is irreversible.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID to delete"}
|
||||
},
|
||||
"required": ["database_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# COLLECTIONS (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_collections",
|
||||
"method_name": "list_collections",
|
||||
"description": "List all collections in a database.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter collections",
|
||||
},
|
||||
},
|
||||
"required": ["database_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_collection",
|
||||
"method_name": "get_collection",
|
||||
"description": "Get collection details including attributes and indexes.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_collection",
|
||||
"method_name": "create_collection",
|
||||
"description": "Create a new collection in a database. Use 'unique()' for auto-generated ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {
|
||||
"type": "string",
|
||||
"description": "Unique collection ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"name": {"type": "string", "description": "Collection name"},
|
||||
"permissions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Collection permissions (e.g., 'read(\"any\")', 'write(\"users\")')",
|
||||
},
|
||||
"document_security": {
|
||||
"type": "boolean",
|
||||
"description": "Enable document-level security",
|
||||
"default": True,
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable or disable the collection",
|
||||
"default": True,
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_collection",
|
||||
"method_name": "update_collection",
|
||||
"description": "Update collection settings.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"name": {"type": "string", "description": "New collection name"},
|
||||
"permissions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "New permissions",
|
||||
},
|
||||
"document_security": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable/disable document-level security",
|
||||
},
|
||||
"enabled": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable/disable the collection",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_collection",
|
||||
"method_name": "delete_collection",
|
||||
"description": "Delete a collection and all its documents. This action is irreversible.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID to delete"},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# ATTRIBUTES (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_attributes",
|
||||
"method_name": "list_attributes",
|
||||
"description": "List all attributes (fields/columns) of a collection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_string_attribute",
|
||||
"method_name": "create_string_attribute",
|
||||
"description": "Create a string attribute in a collection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"key": {"type": "string", "description": "Attribute key (field name)"},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "Maximum string length",
|
||||
"default": 255,
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"description": "Is this field required?",
|
||||
"default": False,
|
||||
},
|
||||
"default": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Default value",
|
||||
},
|
||||
"array": {
|
||||
"type": "boolean",
|
||||
"description": "Is this an array attribute?",
|
||||
"default": False,
|
||||
},
|
||||
"encrypt": {
|
||||
"type": "boolean",
|
||||
"description": "Encrypt the attribute value",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "key"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "create_integer_attribute",
|
||||
"method_name": "create_integer_attribute",
|
||||
"description": "Create an integer attribute in a collection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"key": {"type": "string", "description": "Attribute key (field name)"},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"description": "Is this field required?",
|
||||
"default": False,
|
||||
},
|
||||
"min": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Minimum value",
|
||||
},
|
||||
"max": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Maximum value",
|
||||
},
|
||||
"default": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Default value",
|
||||
},
|
||||
"array": {
|
||||
"type": "boolean",
|
||||
"description": "Is this an array attribute?",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "key"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "create_boolean_attribute",
|
||||
"method_name": "create_boolean_attribute",
|
||||
"description": "Create a boolean attribute in a collection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"key": {"type": "string", "description": "Attribute key (field name)"},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"description": "Is this field required?",
|
||||
"default": False,
|
||||
},
|
||||
"default": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Default value",
|
||||
},
|
||||
"array": {
|
||||
"type": "boolean",
|
||||
"description": "Is this an array attribute?",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "key"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_attribute",
|
||||
"method_name": "delete_attribute",
|
||||
"description": "Delete an attribute from a collection. This will remove the field from all documents.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"key": {"type": "string", "description": "Attribute key to delete"},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "key"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# INDEXES (3)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_indexes",
|
||||
"method_name": "list_indexes",
|
||||
"description": "List all indexes of a collection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_index",
|
||||
"method_name": "create_index",
|
||||
"description": "Create an index on collection attributes for faster queries.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"key": {"type": "string", "description": "Index key (unique name)"},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["key", "unique", "fulltext"],
|
||||
"description": "Index type: key (regular), unique, or fulltext",
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Array of attribute keys to index",
|
||||
},
|
||||
"orders": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string", "enum": ["ASC", "DESC"]}},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Sort order for each attribute (ASC or DESC)",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "key", "type", "attributes"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_index",
|
||||
"method_name": "delete_index",
|
||||
"description": "Delete an index from a collection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"key": {"type": "string", "description": "Index key to delete"},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "key"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_databases(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all databases."""
|
||||
try:
|
||||
result = await client.list_databases(queries=queries, search=search)
|
||||
databases = result.get("databases", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"total": result.get("total", len(databases)),
|
||||
"databases": databases,
|
||||
}
|
||||
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_database(client: AppwriteClient, database_id: str) -> str:
|
||||
"""Get database by ID."""
|
||||
try:
|
||||
result = await client.get_database(database_id)
|
||||
return json.dumps({"success": True, "database": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_database(
|
||||
client: AppwriteClient, database_id: str, name: str, enabled: bool = True
|
||||
) -> str:
|
||||
"""Create a new database."""
|
||||
try:
|
||||
result = await client.create_database(database_id=database_id, name=name, enabled=enabled)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Database '{name}' created successfully",
|
||||
"database": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_database(
|
||||
client: AppwriteClient, database_id: str, name: str, enabled: bool | None = None
|
||||
) -> str:
|
||||
"""Update database."""
|
||||
try:
|
||||
result = await client.update_database(database_id=database_id, name=name, enabled=enabled)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Database updated successfully", "database": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_database(client: AppwriteClient, database_id: str) -> str:
|
||||
"""Delete database."""
|
||||
try:
|
||||
await client.delete_database(database_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Database '{database_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_collections(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
queries: list[str] | None = None,
|
||||
search: str | None = None,
|
||||
) -> str:
|
||||
"""List collections in a database."""
|
||||
try:
|
||||
result = await client.list_collections(
|
||||
database_id=database_id, queries=queries, search=search
|
||||
)
|
||||
collections = result.get("collections", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"database_id": database_id,
|
||||
"total": result.get("total", len(collections)),
|
||||
"collections": collections,
|
||||
}
|
||||
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_collection(client: AppwriteClient, database_id: str, collection_id: str) -> str:
|
||||
"""Get collection details."""
|
||||
try:
|
||||
result = await client.get_collection(database_id, collection_id)
|
||||
return json.dumps({"success": True, "collection": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_collection(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
name: str,
|
||||
permissions: list[str] | None = None,
|
||||
document_security: bool = True,
|
||||
enabled: bool = True,
|
||||
) -> str:
|
||||
"""Create a new collection."""
|
||||
try:
|
||||
result = await client.create_collection(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
name=name,
|
||||
permissions=permissions,
|
||||
document_security=document_security,
|
||||
enabled=enabled,
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Collection '{name}' created successfully",
|
||||
"collection": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_collection(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
name: str,
|
||||
permissions: list[str] | None = None,
|
||||
document_security: bool | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> str:
|
||||
"""Update collection."""
|
||||
try:
|
||||
result = await client.update_collection(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
name=name,
|
||||
permissions=permissions,
|
||||
document_security=document_security,
|
||||
enabled=enabled,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Collection updated successfully", "collection": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_collection(client: AppwriteClient, database_id: str, collection_id: str) -> str:
|
||||
"""Delete collection."""
|
||||
try:
|
||||
await client.delete_collection(database_id, collection_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Collection '{collection_id}' deleted successfully"},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_attributes(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
|
||||
) -> str:
|
||||
"""List collection attributes."""
|
||||
try:
|
||||
result = await client.list_attributes(
|
||||
database_id=database_id, collection_id=collection_id, queries=queries
|
||||
)
|
||||
attributes = result.get("attributes", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"database_id": database_id,
|
||||
"collection_id": collection_id,
|
||||
"total": result.get("total", len(attributes)),
|
||||
"attributes": attributes,
|
||||
}
|
||||
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 create_string_attribute(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
key: str,
|
||||
size: int = 255,
|
||||
required: bool = False,
|
||||
default: str | None = None,
|
||||
array: bool = False,
|
||||
encrypt: bool = False,
|
||||
) -> str:
|
||||
"""Create string attribute."""
|
||||
try:
|
||||
result = await client.create_string_attribute(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
key=key,
|
||||
size=size,
|
||||
required=required,
|
||||
default=default,
|
||||
array=array,
|
||||
encrypt=encrypt,
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"String attribute '{key}' created successfully",
|
||||
"attribute": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_integer_attribute(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
key: str,
|
||||
required: bool = False,
|
||||
min: int | None = None,
|
||||
max: int | None = None,
|
||||
default: int | None = None,
|
||||
array: bool = False,
|
||||
) -> str:
|
||||
"""Create integer attribute."""
|
||||
try:
|
||||
result = await client.create_integer_attribute(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
key=key,
|
||||
required=required,
|
||||
min=min,
|
||||
max=max,
|
||||
default=default,
|
||||
array=array,
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Integer attribute '{key}' created successfully",
|
||||
"attribute": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_boolean_attribute(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
key: str,
|
||||
required: bool = False,
|
||||
default: bool | None = None,
|
||||
array: bool = False,
|
||||
) -> str:
|
||||
"""Create boolean attribute."""
|
||||
try:
|
||||
result = await client.create_boolean_attribute(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
key=key,
|
||||
required=required,
|
||||
default=default,
|
||||
array=array,
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Boolean attribute '{key}' created successfully",
|
||||
"attribute": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_attribute(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, key: str
|
||||
) -> str:
|
||||
"""Delete attribute."""
|
||||
try:
|
||||
await client.delete_attribute(database_id, collection_id, key)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Attribute '{key}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_indexes(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
|
||||
) -> str:
|
||||
"""List collection indexes."""
|
||||
try:
|
||||
result = await client.list_indexes(
|
||||
database_id=database_id, collection_id=collection_id, queries=queries
|
||||
)
|
||||
indexes = result.get("indexes", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"database_id": database_id,
|
||||
"collection_id": collection_id,
|
||||
"total": result.get("total", len(indexes)),
|
||||
"indexes": indexes,
|
||||
}
|
||||
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 create_index(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
key: str,
|
||||
type: str,
|
||||
attributes: list[str],
|
||||
orders: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Create index."""
|
||||
try:
|
||||
result = await client.create_index(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
key=key,
|
||||
type=type,
|
||||
attributes=attributes,
|
||||
orders=orders,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Index '{key}' created successfully", "index": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_index(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, key: str
|
||||
) -> str:
|
||||
"""Delete index."""
|
||||
try:
|
||||
await client.delete_index(database_id, collection_id, key)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Index '{key}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
752
plugins/appwrite/handlers/documents.py
Normal file
752
plugins/appwrite/handlers/documents.py
Normal file
@@ -0,0 +1,752 @@
|
||||
"""
|
||||
Documents Handler - manages Appwrite document CRUD operations
|
||||
|
||||
Phase I.1: 12 tools
|
||||
- Documents CRUD: 5 (list, get, create, update, delete)
|
||||
- Bulk Operations: 3 (bulk_create, bulk_update, bulk_delete)
|
||||
- Query/Count: 4 (search, count, get_by_query, list_with_cursor)
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.appwrite.client import AppwriteClient
|
||||
|
||||
# =====================
|
||||
# QUERY HELPERS (Appwrite 1.7.4 JSON format)
|
||||
# =====================
|
||||
|
||||
def _query_limit(value: int) -> str:
|
||||
"""Build limit query in JSON format."""
|
||||
return json.dumps({"method": "limit", "values": [value]})
|
||||
|
||||
def _query_offset(value: int) -> str:
|
||||
"""Build offset query in JSON format."""
|
||||
return json.dumps({"method": "offset", "values": [value]})
|
||||
|
||||
def _query_order_asc(attribute: str) -> str:
|
||||
"""Build orderAsc query in JSON format."""
|
||||
return json.dumps({"method": "orderAsc", "values": [attribute]})
|
||||
|
||||
def _query_order_desc(attribute: str) -> str:
|
||||
"""Build orderDesc query in JSON format."""
|
||||
return json.dumps({"method": "orderDesc", "values": [attribute]})
|
||||
|
||||
def _query_cursor_after(document_id: str) -> str:
|
||||
"""Build cursorAfter query in JSON format."""
|
||||
return json.dumps({"method": "cursorAfter", "values": [document_id]})
|
||||
|
||||
def _query_cursor_before(document_id: str) -> str:
|
||||
"""Build cursorBefore query in JSON format."""
|
||||
return json.dumps({"method": "cursorBefore", "values": [document_id]})
|
||||
|
||||
def _query_search(attribute: str, value: str) -> str:
|
||||
"""Build search query in JSON format."""
|
||||
return json.dumps({"method": "search", "attribute": attribute, "values": [value]})
|
||||
|
||||
def _query_equal(attribute: str, values: list[Any]) -> str:
|
||||
"""Build equal query in JSON format."""
|
||||
return json.dumps({"method": "equal", "attribute": attribute, "values": values})
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator (12 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# DOCUMENT CRUD (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_documents",
|
||||
"method_name": "list_documents",
|
||||
"description": """List documents in a collection with powerful query support.
|
||||
|
||||
Queries must be JSON objects (Appwrite 1.7.4 format):
|
||||
- {"method": "equal", "attribute": "status", "values": ["active"]}
|
||||
- {"method": "notEqual", "attribute": "type", "values": ["draft"]}
|
||||
- {"method": "greaterThan", "attribute": "price", "values": [100]}
|
||||
- {"method": "search", "attribute": "title", "values": ["keyword"]}
|
||||
- {"method": "orderAsc", "values": ["createdAt"]}
|
||||
- {"method": "orderDesc", "values": ["createdAt"]}
|
||||
- {"method": "limit", "values": [25]}
|
||||
- {"method": "offset", "values": [50]}
|
||||
- {"method": "cursorAfter", "values": ["documentId"]}""",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query JSON strings for filtering, sorting, and pagination",
|
||||
"examples": [
|
||||
[
|
||||
'{"method":"equal","attribute":"status","values":["active"]}',
|
||||
'{"method":"limit","values":[25]}',
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_document",
|
||||
"method_name": "get_document",
|
||||
"description": "Get a single document by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"document_id": {"type": "string", "description": "Document ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Optional queries for selecting specific attributes",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "document_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_document",
|
||||
"method_name": "create_document",
|
||||
"description": "Create a new document. Use 'unique()' as document_id for auto-generated ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"document_id": {
|
||||
"type": "string",
|
||||
"description": "Unique document ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Document data (key-value pairs matching collection attributes)",
|
||||
},
|
||||
"permissions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Document permissions (e.g., 'read(\"user:123\")', 'write(\"team:456\")')",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "document_id", "data"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_document",
|
||||
"method_name": "update_document",
|
||||
"description": "Update an existing document. Only provided fields will be updated.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"document_id": {"type": "string", "description": "Document ID"},
|
||||
"data": {
|
||||
"anyOf": [{"type": "object"}, {"type": "null"}],
|
||||
"description": "Fields to update",
|
||||
},
|
||||
"permissions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "New permissions (replaces existing)",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "document_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_document",
|
||||
"method_name": "delete_document",
|
||||
"description": "Delete a document by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"document_id": {"type": "string", "description": "Document ID to delete"},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "document_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# =====================
|
||||
# BULK OPERATIONS (3)
|
||||
# =====================
|
||||
{
|
||||
"name": "bulk_create_documents",
|
||||
"method_name": "bulk_create_documents",
|
||||
"description": """Create multiple documents at once. More efficient than creating one by one.
|
||||
|
||||
Example documents array:
|
||||
[
|
||||
{"document_id": "unique()", "data": {"title": "Doc 1", "status": "active"}},
|
||||
{"document_id": "doc-2", "data": {"title": "Doc 2", "status": "draft"}, "permissions": ["read(\\"any\\")"]}
|
||||
]
|
||||
|
||||
Each document must have:
|
||||
- document_id: Use 'unique()' for auto-generated ID or provide your own
|
||||
- data: Object with field values matching collection attributes""",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"documents": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"document_id": {
|
||||
"type": "string",
|
||||
"description": "Document ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Document data (key-value pairs matching collection attributes)",
|
||||
},
|
||||
"permissions": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Optional permissions for this document",
|
||||
},
|
||||
},
|
||||
"required": ["document_id", "data"],
|
||||
},
|
||||
"description": "Array of documents to create. Each must have document_id and data.",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "documents"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "bulk_update_documents",
|
||||
"method_name": "bulk_update_documents",
|
||||
"description": "Update multiple documents at once.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"updates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"document_id": {"type": "string"},
|
||||
"data": {"type": "object"},
|
||||
"permissions": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "null"},
|
||||
]
|
||||
},
|
||||
},
|
||||
"required": ["document_id", "data"],
|
||||
},
|
||||
"description": "Array of document updates",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "updates"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "bulk_delete_documents",
|
||||
"method_name": "bulk_delete_documents",
|
||||
"description": "Delete multiple documents at once.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"document_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Array of document IDs to delete",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "document_ids"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# =====================
|
||||
# QUERY/SEARCH (4)
|
||||
# =====================
|
||||
{
|
||||
"name": "search_documents",
|
||||
"method_name": "search_documents",
|
||||
"description": "Full-text search in documents. Requires a fulltext index on the searched attribute.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"attribute": {
|
||||
"type": "string",
|
||||
"description": "Attribute name to search in (must have fulltext index)",
|
||||
},
|
||||
"query": {"type": "string", "description": "Search query string"},
|
||||
"limit": {"type": "integer", "description": "Maximum results", "default": 25},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "attribute", "query"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "count_documents",
|
||||
"method_name": "count_documents",
|
||||
"description": "Count documents in a collection with optional filters.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Optional filter queries",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_documents_by_ids",
|
||||
"method_name": "get_documents_by_ids",
|
||||
"description": "Get multiple documents by their IDs.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"document_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Array of document IDs to fetch",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id", "document_ids"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_documents_paginated",
|
||||
"method_name": "list_documents_paginated",
|
||||
"description": "List documents with cursor-based pagination for efficient large dataset traversal.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"database_id": {"type": "string", "description": "Database ID"},
|
||||
"collection_id": {"type": "string", "description": "Collection ID"},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Results per page",
|
||||
"default": 25,
|
||||
"maximum": 100,
|
||||
},
|
||||
"cursor": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Cursor from previous response for next page",
|
||||
},
|
||||
"cursor_direction": {
|
||||
"type": "string",
|
||||
"enum": ["after", "before"],
|
||||
"description": "Cursor direction",
|
||||
"default": "after",
|
||||
},
|
||||
"order_attribute": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Attribute to order by (default: $id)",
|
||||
},
|
||||
"order_type": {
|
||||
"type": "string",
|
||||
"enum": ["ASC", "DESC"],
|
||||
"description": "Sort order",
|
||||
"default": "DESC",
|
||||
},
|
||||
"filters": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Additional filter queries",
|
||||
},
|
||||
},
|
||||
"required": ["database_id", "collection_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_documents(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
|
||||
) -> str:
|
||||
"""List documents with queries."""
|
||||
try:
|
||||
result = await client.list_documents(
|
||||
database_id=database_id, collection_id=collection_id, queries=queries
|
||||
)
|
||||
documents = result.get("documents", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"database_id": database_id,
|
||||
"collection_id": collection_id,
|
||||
"total": result.get("total", len(documents)),
|
||||
"documents": documents,
|
||||
}
|
||||
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_document(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
document_id: str,
|
||||
queries: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Get document by ID."""
|
||||
try:
|
||||
result = await client.get_document(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
document_id=document_id,
|
||||
queries=queries,
|
||||
)
|
||||
return json.dumps({"success": True, "document": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_document(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
document_id: str,
|
||||
data: dict[str, Any],
|
||||
permissions: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Create a new document."""
|
||||
try:
|
||||
result = await client.create_document(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
document_id=document_id,
|
||||
data=data,
|
||||
permissions=permissions,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Document created successfully", "document": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_document(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
document_id: str,
|
||||
data: dict[str, Any] | None = None,
|
||||
permissions: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Update document."""
|
||||
try:
|
||||
result = await client.update_document(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
document_id=document_id,
|
||||
data=data,
|
||||
permissions=permissions,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Document updated successfully", "document": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_document(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, document_id: str
|
||||
) -> str:
|
||||
"""Delete document."""
|
||||
try:
|
||||
await client.delete_document(database_id, collection_id, document_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Document '{document_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def bulk_create_documents(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, documents: list[dict[str, Any]]
|
||||
) -> str:
|
||||
"""Create multiple documents."""
|
||||
try:
|
||||
results = []
|
||||
errors = []
|
||||
|
||||
for doc in documents:
|
||||
try:
|
||||
result = await client.create_document(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
document_id=doc.get("document_id", "unique()"),
|
||||
data=doc["data"],
|
||||
permissions=doc.get("permissions"),
|
||||
)
|
||||
results.append({"id": result.get("$id"), "success": True})
|
||||
except Exception as e:
|
||||
errors.append({"document_id": doc.get("document_id"), "error": str(e)})
|
||||
|
||||
response = {
|
||||
"success": len(errors) == 0,
|
||||
"created": len(results),
|
||||
"failed": len(errors),
|
||||
"results": results,
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
|
||||
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 bulk_update_documents(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, updates: list[dict[str, Any]]
|
||||
) -> str:
|
||||
"""Update multiple documents."""
|
||||
try:
|
||||
results = []
|
||||
errors = []
|
||||
|
||||
for update in updates:
|
||||
try:
|
||||
result = await client.update_document(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
document_id=update["document_id"],
|
||||
data=update.get("data"),
|
||||
permissions=update.get("permissions"),
|
||||
)
|
||||
results.append({"id": result.get("$id"), "success": True})
|
||||
except Exception as e:
|
||||
errors.append({"document_id": update["document_id"], "error": str(e)})
|
||||
|
||||
response = {
|
||||
"success": len(errors) == 0,
|
||||
"updated": len(results),
|
||||
"failed": len(errors),
|
||||
"results": results,
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
|
||||
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 bulk_delete_documents(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, document_ids: list[str]
|
||||
) -> str:
|
||||
"""Delete multiple documents."""
|
||||
try:
|
||||
results = []
|
||||
errors = []
|
||||
|
||||
for doc_id in document_ids:
|
||||
try:
|
||||
await client.delete_document(database_id, collection_id, doc_id)
|
||||
results.append({"id": doc_id, "success": True})
|
||||
except Exception as e:
|
||||
errors.append({"document_id": doc_id, "error": str(e)})
|
||||
|
||||
response = {
|
||||
"success": len(errors) == 0,
|
||||
"deleted": len(results),
|
||||
"failed": len(errors),
|
||||
"results": results,
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
|
||||
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 search_documents(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
attribute: str,
|
||||
query: str,
|
||||
limit: int = 25,
|
||||
) -> str:
|
||||
"""Full-text search in documents."""
|
||||
try:
|
||||
# Appwrite 1.7.4 JSON format for queries
|
||||
queries = [_query_search(attribute, query), _query_limit(limit)]
|
||||
result = await client.list_documents(
|
||||
database_id=database_id, collection_id=collection_id, queries=queries
|
||||
)
|
||||
documents = result.get("documents", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"query": query,
|
||||
"attribute": attribute,
|
||||
"total": result.get("total", len(documents)),
|
||||
"documents": documents,
|
||||
}
|
||||
return json.dumps(response, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "fulltext" in error_msg.lower() or "index" in error_msg.lower():
|
||||
error_msg += " (Hint: Make sure you have a fulltext index on the searched attribute)"
|
||||
return json.dumps({"success": False, "error": error_msg}, indent=2)
|
||||
|
||||
async def count_documents(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, queries: list[str] | None = None
|
||||
) -> str:
|
||||
"""Count documents in collection."""
|
||||
try:
|
||||
# Build query list: start with user filters, add limit(1) to minimize data
|
||||
count_queries = []
|
||||
|
||||
# Add user-provided filter queries (make a copy to avoid mutating input)
|
||||
if queries and len(queries) > 0:
|
||||
count_queries.extend(queries)
|
||||
|
||||
# Always add limit(1) to minimize data transfer - Appwrite 1.7.4 JSON format
|
||||
count_queries.append(_query_limit(1))
|
||||
|
||||
result = await client.list_documents(
|
||||
database_id=database_id, collection_id=collection_id, queries=count_queries
|
||||
)
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"database_id": database_id,
|
||||
"collection_id": collection_id,
|
||||
"count": result.get("total", 0),
|
||||
}
|
||||
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_documents_by_ids(
|
||||
client: AppwriteClient, database_id: str, collection_id: str, document_ids: list[str]
|
||||
) -> str:
|
||||
"""Get multiple documents by IDs."""
|
||||
try:
|
||||
documents = []
|
||||
errors = []
|
||||
|
||||
for doc_id in document_ids:
|
||||
try:
|
||||
doc = await client.get_document(
|
||||
database_id=database_id, collection_id=collection_id, document_id=doc_id
|
||||
)
|
||||
documents.append(doc)
|
||||
except Exception as e:
|
||||
errors.append({"document_id": doc_id, "error": str(e)})
|
||||
|
||||
response = {
|
||||
"success": len(errors) == 0,
|
||||
"found": len(documents),
|
||||
"not_found": len(errors),
|
||||
"documents": documents,
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
|
||||
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 list_documents_paginated(
|
||||
client: AppwriteClient,
|
||||
database_id: str,
|
||||
collection_id: str,
|
||||
limit: int = 25,
|
||||
cursor: str | None = None,
|
||||
cursor_direction: str = "after",
|
||||
order_attribute: str | None = None,
|
||||
order_type: str = "DESC",
|
||||
filters: list[str] | None = None,
|
||||
) -> str:
|
||||
"""List documents with cursor pagination."""
|
||||
try:
|
||||
# Build query list (don't mutate input) - Appwrite 1.7.4 JSON format
|
||||
queries = []
|
||||
|
||||
# Add user-provided filter queries
|
||||
if filters and len(filters) > 0:
|
||||
queries.extend(filters)
|
||||
|
||||
# Add ordering only if explicitly specified
|
||||
if order_attribute:
|
||||
if order_type == "DESC":
|
||||
queries.append(_query_order_desc(order_attribute))
|
||||
else:
|
||||
queries.append(_query_order_asc(order_attribute))
|
||||
|
||||
# Add cursor
|
||||
if cursor:
|
||||
if cursor_direction == "after":
|
||||
queries.append(_query_cursor_after(cursor))
|
||||
else:
|
||||
queries.append(_query_cursor_before(cursor))
|
||||
|
||||
# Add limit only if queries exist or limit differs from default
|
||||
if queries or limit != 25:
|
||||
queries.append(_query_limit(min(limit, 100)))
|
||||
|
||||
result = await client.list_documents(
|
||||
database_id=database_id,
|
||||
collection_id=collection_id,
|
||||
queries=queries if queries else None,
|
||||
)
|
||||
documents = result.get("documents", [])
|
||||
|
||||
# Determine next cursor
|
||||
next_cursor = None
|
||||
if documents and len(documents) == limit:
|
||||
next_cursor = documents[-1].get("$id")
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"total": result.get("total", 0),
|
||||
"count": len(documents),
|
||||
"documents": documents,
|
||||
"pagination": {
|
||||
"limit": limit,
|
||||
"cursor": cursor,
|
||||
"next_cursor": next_cursor,
|
||||
"has_more": next_cursor is not None,
|
||||
},
|
||||
}
|
||||
return json.dumps(response, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
629
plugins/appwrite/handlers/functions.py
Normal file
629
plugins/appwrite/handlers/functions.py
Normal file
@@ -0,0 +1,629 @@
|
||||
"""
|
||||
Functions Handler - manages Appwrite serverless functions
|
||||
|
||||
Phase I.4: 14 tools
|
||||
- Functions: 5 (list, get, create, update, delete)
|
||||
- Deployments: 5 (list, get, create, delete, activate)
|
||||
- Executions: 4 (list, get, create, delete)
|
||||
"""
|
||||
|
||||
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 (14 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# FUNCTIONS (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_functions",
|
||||
"method_name": "list_functions",
|
||||
"description": "List all serverless functions in the project.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter functions",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_function",
|
||||
"method_name": "get_function",
|
||||
"description": "Get function details including deployment status and configuration.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"function_id": {"type": "string", "description": "Function ID"}},
|
||||
"required": ["function_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_function",
|
||||
"method_name": "create_function",
|
||||
"description": "Create a new serverless function.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {
|
||||
"type": "string",
|
||||
"description": "Unique function ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"name": {"type": "string", "description": "Function name"},
|
||||
"runtime": {
|
||||
"type": "string",
|
||||
"description": "Runtime (e.g., 'node-18.0', 'python-3.9', 'php-8.0', 'ruby-3.0')",
|
||||
"examples": [
|
||||
"node-18.0",
|
||||
"node-20.0",
|
||||
"python-3.9",
|
||||
"python-3.11",
|
||||
"php-8.0",
|
||||
"php-8.2",
|
||||
"ruby-3.0",
|
||||
"go-1.21",
|
||||
"dart-3.0",
|
||||
"rust-1.70",
|
||||
],
|
||||
},
|
||||
"execute": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Execution permissions (e.g., ['any', 'users'])",
|
||||
},
|
||||
"events": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Events to trigger function (e.g., ['databases.*.collections.*.documents.*.create'])",
|
||||
},
|
||||
"schedule": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Cron schedule (e.g., '0 * * * *' for every hour)",
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"description": "Execution timeout in seconds",
|
||||
"default": 15,
|
||||
"minimum": 1,
|
||||
"maximum": 900,
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable function",
|
||||
"default": True,
|
||||
},
|
||||
"logging": {
|
||||
"type": "boolean",
|
||||
"description": "Enable logging",
|
||||
"default": True,
|
||||
},
|
||||
"entrypoint": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Entrypoint file (e.g., 'src/main.js')",
|
||||
},
|
||||
"commands": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Build commands",
|
||||
},
|
||||
},
|
||||
"required": ["function_id", "name", "runtime"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_function",
|
||||
"method_name": "update_function",
|
||||
"description": "Update function configuration.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"name": {"type": "string", "description": "Function name"},
|
||||
"runtime": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "New runtime",
|
||||
},
|
||||
"execute": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "New execution permissions",
|
||||
},
|
||||
"events": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "New trigger events",
|
||||
},
|
||||
"schedule": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "New cron schedule",
|
||||
},
|
||||
"timeout": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "New timeout",
|
||||
},
|
||||
"enabled": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable/disable function",
|
||||
},
|
||||
},
|
||||
"required": ["function_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_function",
|
||||
"method_name": "delete_function",
|
||||
"description": "Delete a function and all its deployments.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID to delete"}
|
||||
},
|
||||
"required": ["function_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# DEPLOYMENTS (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_deployments",
|
||||
"method_name": "list_deployments",
|
||||
"description": "List all deployments of a function.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term",
|
||||
},
|
||||
},
|
||||
"required": ["function_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_deployment",
|
||||
"method_name": "get_deployment",
|
||||
"description": "Get deployment details including build status and logs.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"deployment_id": {"type": "string", "description": "Deployment ID"},
|
||||
},
|
||||
"required": ["function_id", "deployment_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "delete_deployment",
|
||||
"method_name": "delete_deployment",
|
||||
"description": "Delete a deployment.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"deployment_id": {"type": "string", "description": "Deployment ID to delete"},
|
||||
},
|
||||
"required": ["function_id", "deployment_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "activate_deployment",
|
||||
"method_name": "activate_deployment",
|
||||
"description": "Activate a deployment (set as the active version for the function).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"deployment_id": {"type": "string", "description": "Deployment ID to activate"},
|
||||
},
|
||||
"required": ["function_id", "deployment_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "get_active_deployment",
|
||||
"method_name": "get_active_deployment",
|
||||
"description": "Get the currently active deployment for a function.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"function_id": {"type": "string", "description": "Function ID"}},
|
||||
"required": ["function_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# =====================
|
||||
# EXECUTIONS (4)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_executions",
|
||||
"method_name": "list_executions",
|
||||
"description": "List execution history for a function.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term",
|
||||
},
|
||||
},
|
||||
"required": ["function_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_execution",
|
||||
"method_name": "get_execution",
|
||||
"description": "Get execution details including response, logs, and timing.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"execution_id": {"type": "string", "description": "Execution ID"},
|
||||
},
|
||||
"required": ["function_id", "execution_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "execute_function",
|
||||
"method_name": "execute_function",
|
||||
"description": "Execute a function immediately.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"body": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Request body (JSON string)",
|
||||
},
|
||||
"async_execution": {
|
||||
"type": "boolean",
|
||||
"description": "Run asynchronously (don't wait for response)",
|
||||
"default": False,
|
||||
},
|
||||
"path": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Custom execution path",
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
||||
"description": "HTTP method",
|
||||
"default": "POST",
|
||||
},
|
||||
"headers": {
|
||||
"anyOf": [{"type": "object"}, {"type": "null"}],
|
||||
"description": "Custom headers",
|
||||
},
|
||||
},
|
||||
"required": ["function_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_execution",
|
||||
"method_name": "delete_execution",
|
||||
"description": "Delete an execution log entry.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"function_id": {"type": "string", "description": "Function ID"},
|
||||
"execution_id": {"type": "string", "description": "Execution ID to delete"},
|
||||
},
|
||||
"required": ["function_id", "execution_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_functions(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all functions."""
|
||||
try:
|
||||
result = await client.list_functions(queries=queries, search=search)
|
||||
functions = result.get("functions", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"total": result.get("total", len(functions)),
|
||||
"functions": functions,
|
||||
}
|
||||
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_function(client: AppwriteClient, function_id: str) -> str:
|
||||
"""Get function by ID."""
|
||||
try:
|
||||
result = await client.get_function(function_id)
|
||||
return json.dumps({"success": True, "function": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_function(
|
||||
client: AppwriteClient,
|
||||
function_id: str,
|
||||
name: str,
|
||||
runtime: str,
|
||||
execute: list[str] | None = None,
|
||||
events: list[str] | None = None,
|
||||
schedule: str | None = None,
|
||||
timeout: int = 15,
|
||||
enabled: bool = True,
|
||||
logging: bool = True,
|
||||
entrypoint: str | None = None,
|
||||
commands: str | None = None,
|
||||
) -> str:
|
||||
"""Create a new function."""
|
||||
try:
|
||||
result = await client.create_function(
|
||||
function_id=function_id,
|
||||
name=name,
|
||||
runtime=runtime,
|
||||
execute=execute,
|
||||
events=events,
|
||||
schedule=schedule,
|
||||
timeout=timeout,
|
||||
enabled=enabled,
|
||||
logging=logging,
|
||||
entrypoint=entrypoint,
|
||||
commands=commands,
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Function '{name}' created successfully",
|
||||
"function": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_function(
|
||||
client: AppwriteClient,
|
||||
function_id: str,
|
||||
name: str,
|
||||
runtime: str | None = None,
|
||||
execute: list[str] | None = None,
|
||||
events: list[str] | None = None,
|
||||
schedule: str | None = None,
|
||||
timeout: int | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> str:
|
||||
"""Update function."""
|
||||
try:
|
||||
result = await client.update_function(
|
||||
function_id=function_id,
|
||||
name=name,
|
||||
runtime=runtime,
|
||||
execute=execute,
|
||||
events=events,
|
||||
schedule=schedule,
|
||||
timeout=timeout,
|
||||
enabled=enabled,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Function updated successfully", "function": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_function(client: AppwriteClient, function_id: str) -> str:
|
||||
"""Delete function."""
|
||||
try:
|
||||
await client.delete_function(function_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Function '{function_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_deployments(
|
||||
client: AppwriteClient,
|
||||
function_id: str,
|
||||
queries: list[str] | None = None,
|
||||
search: str | None = None,
|
||||
) -> str:
|
||||
"""List function deployments."""
|
||||
try:
|
||||
result = await client.list_deployments(
|
||||
function_id=function_id, queries=queries, search=search
|
||||
)
|
||||
deployments = result.get("deployments", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"function_id": function_id,
|
||||
"total": result.get("total", len(deployments)),
|
||||
"deployments": deployments,
|
||||
}
|
||||
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_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
|
||||
"""Get deployment by ID."""
|
||||
try:
|
||||
result = await client.get_deployment(function_id, deployment_id)
|
||||
return json.dumps({"success": True, "deployment": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
|
||||
"""Delete deployment."""
|
||||
try:
|
||||
await client.delete_deployment(function_id, deployment_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Deployment '{deployment_id}' deleted successfully"},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def activate_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
|
||||
"""Activate deployment."""
|
||||
try:
|
||||
result = await client.update_deployment(function_id, deployment_id)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Deployment '{deployment_id}' activated successfully",
|
||||
"deployment": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_active_deployment(client: AppwriteClient, function_id: str) -> str:
|
||||
"""Get active deployment for function."""
|
||||
try:
|
||||
func = await client.get_function(function_id)
|
||||
deployment_id = func.get("deployment")
|
||||
|
||||
if not deployment_id:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": "No active deployment",
|
||||
"function_id": function_id,
|
||||
"active_deployment": None,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
deployment = await client.get_deployment(function_id, deployment_id)
|
||||
return json.dumps(
|
||||
{"success": True, "function_id": function_id, "active_deployment": deployment},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_executions(
|
||||
client: AppwriteClient,
|
||||
function_id: str,
|
||||
queries: list[str] | None = None,
|
||||
search: str | None = None,
|
||||
) -> str:
|
||||
"""List function executions."""
|
||||
try:
|
||||
result = await client.list_executions(
|
||||
function_id=function_id, queries=queries, search=search
|
||||
)
|
||||
executions = result.get("executions", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"function_id": function_id,
|
||||
"total": result.get("total", len(executions)),
|
||||
"executions": executions,
|
||||
}
|
||||
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_execution(client: AppwriteClient, function_id: str, execution_id: str) -> str:
|
||||
"""Get execution by ID."""
|
||||
try:
|
||||
result = await client.get_execution(function_id, execution_id)
|
||||
return json.dumps({"success": True, "execution": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def execute_function(
|
||||
client: AppwriteClient,
|
||||
function_id: str,
|
||||
body: str | None = None,
|
||||
async_execution: bool = False,
|
||||
path: str | None = None,
|
||||
method: str = "POST",
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> str:
|
||||
"""Execute function."""
|
||||
try:
|
||||
result = await client.create_execution(
|
||||
function_id=function_id,
|
||||
body=body,
|
||||
async_execution=async_execution,
|
||||
path=path,
|
||||
method=method,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"execution_id": result.get("$id"),
|
||||
"status": result.get("status"),
|
||||
"status_code": result.get("responseStatusCode"),
|
||||
"duration": result.get("duration"),
|
||||
"response_body": result.get("responseBody"),
|
||||
"logs": result.get("logs"),
|
||||
"errors": result.get("errors"),
|
||||
}
|
||||
|
||||
if async_execution:
|
||||
response["message"] = "Function execution started asynchronously"
|
||||
else:
|
||||
response["message"] = "Function executed successfully"
|
||||
|
||||
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 delete_execution(client: AppwriteClient, function_id: str, execution_id: str) -> str:
|
||||
"""Delete execution."""
|
||||
try:
|
||||
await client.delete_execution(function_id, execution_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Execution '{execution_id}' deleted successfully"},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
579
plugins/appwrite/handlers/messaging.py
Normal file
579
plugins/appwrite/handlers/messaging.py
Normal file
@@ -0,0 +1,579 @@
|
||||
"""
|
||||
Messaging Handler - manages Appwrite messaging (Email, SMS, Push notifications)
|
||||
|
||||
Phase I.4: 12 tools
|
||||
- Topics: 4 (list, get, create, delete)
|
||||
- Subscribers: 2 (create, delete)
|
||||
- Messages: 6 (list, get, send_email, send_sms, send_push, delete)
|
||||
"""
|
||||
|
||||
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 (12 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# TOPICS (4)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_topics",
|
||||
"method_name": "list_topics",
|
||||
"description": "List all messaging topics. Topics are groups for sending messages to multiple subscribers.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_topic",
|
||||
"method_name": "get_topic",
|
||||
"description": "Get topic details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"topic_id": {"type": "string", "description": "Topic ID"}},
|
||||
"required": ["topic_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_topic",
|
||||
"method_name": "create_topic",
|
||||
"description": "Create a new messaging topic.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_id": {
|
||||
"type": "string",
|
||||
"description": "Unique topic ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"name": {"type": "string", "description": "Topic name"},
|
||||
"subscribe": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Permissions for who can subscribe (e.g., ['users', 'any'])",
|
||||
},
|
||||
},
|
||||
"required": ["topic_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_topic",
|
||||
"method_name": "delete_topic",
|
||||
"description": "Delete a messaging topic.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"topic_id": {"type": "string", "description": "Topic ID to delete"}},
|
||||
"required": ["topic_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# =====================
|
||||
# SUBSCRIBERS (2)
|
||||
# =====================
|
||||
{
|
||||
"name": "create_subscriber",
|
||||
"method_name": "create_subscriber",
|
||||
"description": "Add a subscriber to a topic.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_id": {"type": "string", "description": "Topic ID"},
|
||||
"subscriber_id": {
|
||||
"type": "string",
|
||||
"description": "Unique subscriber ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"target_id": {
|
||||
"type": "string",
|
||||
"description": "Target ID (user's target for messaging)",
|
||||
},
|
||||
},
|
||||
"required": ["topic_id", "subscriber_id", "target_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_subscriber",
|
||||
"method_name": "delete_subscriber",
|
||||
"description": "Remove a subscriber from a topic.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_id": {"type": "string", "description": "Topic ID"},
|
||||
"subscriber_id": {"type": "string", "description": "Subscriber ID to remove"},
|
||||
},
|
||||
"required": ["topic_id", "subscriber_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# =====================
|
||||
# MESSAGES (6)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_messages",
|
||||
"method_name": "list_messages",
|
||||
"description": "List all messages (sent and draft).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_message",
|
||||
"method_name": "get_message",
|
||||
"description": "Get message details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"message_id": {"type": "string", "description": "Message ID"}},
|
||||
"required": ["message_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "send_email",
|
||||
"method_name": "send_email",
|
||||
"description": "Send an email message. Requires configured email provider.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message_id": {
|
||||
"type": "string",
|
||||
"description": "Unique message ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"subject": {"type": "string", "description": "Email subject"},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Email content (HTML supported if html=true)",
|
||||
},
|
||||
"topics": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Topic IDs to send to",
|
||||
},
|
||||
"users": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "User IDs to send to",
|
||||
},
|
||||
"targets": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Target IDs to send to",
|
||||
},
|
||||
"cc": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "CC recipients",
|
||||
},
|
||||
"bcc": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "BCC recipients",
|
||||
},
|
||||
"html": {
|
||||
"type": "boolean",
|
||||
"description": "Send as HTML email",
|
||||
"default": True,
|
||||
},
|
||||
"draft": {
|
||||
"type": "boolean",
|
||||
"description": "Save as draft instead of sending",
|
||||
"default": False,
|
||||
},
|
||||
"scheduled_at": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Schedule send time (ISO 8601 format)",
|
||||
},
|
||||
},
|
||||
"required": ["message_id", "subject", "content"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "send_sms",
|
||||
"method_name": "send_sms",
|
||||
"description": "Send an SMS message. Requires configured SMS provider.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message_id": {
|
||||
"type": "string",
|
||||
"description": "Unique message ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"content": {"type": "string", "description": "SMS content"},
|
||||
"topics": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Topic IDs to send to",
|
||||
},
|
||||
"users": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "User IDs to send to",
|
||||
},
|
||||
"targets": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Target IDs to send to",
|
||||
},
|
||||
"draft": {"type": "boolean", "description": "Save as draft", "default": False},
|
||||
"scheduled_at": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Schedule send time (ISO 8601 format)",
|
||||
},
|
||||
},
|
||||
"required": ["message_id", "content"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "send_push",
|
||||
"method_name": "send_push",
|
||||
"description": "Send a push notification. Requires configured push provider (FCM/APNs).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message_id": {
|
||||
"type": "string",
|
||||
"description": "Unique message ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"title": {"type": "string", "description": "Notification title"},
|
||||
"body": {"type": "string", "description": "Notification body"},
|
||||
"topics": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Topic IDs to send to",
|
||||
},
|
||||
"users": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "User IDs to send to",
|
||||
},
|
||||
"targets": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Target IDs to send to",
|
||||
},
|
||||
"data": {
|
||||
"anyOf": [{"type": "object"}, {"type": "null"}],
|
||||
"description": "Custom data payload",
|
||||
},
|
||||
"action": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Action URL",
|
||||
},
|
||||
"image": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Image URL",
|
||||
},
|
||||
"icon": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Icon URL",
|
||||
},
|
||||
"sound": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Sound name",
|
||||
},
|
||||
"badge": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Badge number (iOS)",
|
||||
},
|
||||
"draft": {"type": "boolean", "description": "Save as draft", "default": False},
|
||||
"scheduled_at": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Schedule send time (ISO 8601 format)",
|
||||
},
|
||||
},
|
||||
"required": ["message_id", "title", "body"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_message",
|
||||
"method_name": "delete_message",
|
||||
"description": "Delete a message.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message_id": {"type": "string", "description": "Message ID to delete"}
|
||||
},
|
||||
"required": ["message_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_topics(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all topics."""
|
||||
try:
|
||||
result = await client.list_topics(queries=queries, search=search)
|
||||
topics = result.get("topics", [])
|
||||
|
||||
response = {"success": True, "total": result.get("total", len(topics)), "topics": topics}
|
||||
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_topic(client: AppwriteClient, topic_id: str) -> str:
|
||||
"""Get topic by ID."""
|
||||
try:
|
||||
result = await client.get_topic(topic_id)
|
||||
return json.dumps({"success": True, "topic": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_topic(
|
||||
client: AppwriteClient, topic_id: str, name: str, subscribe: list[str] | None = None
|
||||
) -> str:
|
||||
"""Create a new topic."""
|
||||
try:
|
||||
result = await client.create_topic(topic_id=topic_id, name=name, subscribe=subscribe)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Topic '{name}' created successfully", "topic": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_topic(client: AppwriteClient, topic_id: str) -> str:
|
||||
"""Delete topic."""
|
||||
try:
|
||||
await client.delete_topic(topic_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Topic '{topic_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_subscriber(
|
||||
client: AppwriteClient, topic_id: str, subscriber_id: str, target_id: str
|
||||
) -> str:
|
||||
"""Add subscriber to topic."""
|
||||
try:
|
||||
result = await client.create_subscriber(
|
||||
topic_id=topic_id, subscriber_id=subscriber_id, target_id=target_id
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Subscriber added to topic", "subscriber": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_subscriber(client: AppwriteClient, topic_id: str, subscriber_id: str) -> str:
|
||||
"""Remove subscriber from topic."""
|
||||
try:
|
||||
await client.delete_subscriber(topic_id, subscriber_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Subscriber '{subscriber_id}' removed from topic"},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_messages(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all messages."""
|
||||
try:
|
||||
result = await client.list_messages(queries=queries, search=search)
|
||||
messages = result.get("messages", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"total": result.get("total", len(messages)),
|
||||
"messages": messages,
|
||||
}
|
||||
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_message(client: AppwriteClient, message_id: str) -> str:
|
||||
"""Get message by ID."""
|
||||
try:
|
||||
result = await client.get_message(message_id)
|
||||
return json.dumps({"success": True, "message": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def send_email(
|
||||
client: AppwriteClient,
|
||||
message_id: str,
|
||||
subject: str,
|
||||
content: str,
|
||||
topics: list[str] | None = None,
|
||||
users: list[str] | None = None,
|
||||
targets: list[str] | None = None,
|
||||
cc: list[str] | None = None,
|
||||
bcc: list[str] | None = None,
|
||||
html: bool = True,
|
||||
draft: bool = False,
|
||||
scheduled_at: str | None = None,
|
||||
) -> str:
|
||||
"""Send email message."""
|
||||
try:
|
||||
result = await client.create_email(
|
||||
message_id=message_id,
|
||||
subject=subject,
|
||||
content=content,
|
||||
topics=topics,
|
||||
users=users,
|
||||
targets=targets,
|
||||
cc=cc,
|
||||
bcc=bcc,
|
||||
html=html,
|
||||
draft=draft,
|
||||
scheduled_at=scheduled_at,
|
||||
)
|
||||
|
||||
action = "saved as draft" if draft else "sent"
|
||||
if scheduled_at:
|
||||
action = f"scheduled for {scheduled_at}"
|
||||
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Email {action} successfully", "email": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "provider" in error_msg.lower():
|
||||
error_msg += " (Hint: Make sure you have configured an email provider in Appwrite)"
|
||||
return json.dumps({"success": False, "error": error_msg}, indent=2)
|
||||
|
||||
async def send_sms(
|
||||
client: AppwriteClient,
|
||||
message_id: str,
|
||||
content: str,
|
||||
topics: list[str] | None = None,
|
||||
users: list[str] | None = None,
|
||||
targets: list[str] | None = None,
|
||||
draft: bool = False,
|
||||
scheduled_at: str | None = None,
|
||||
) -> str:
|
||||
"""Send SMS message."""
|
||||
try:
|
||||
result = await client.create_sms(
|
||||
message_id=message_id,
|
||||
content=content,
|
||||
topics=topics,
|
||||
users=users,
|
||||
targets=targets,
|
||||
draft=draft,
|
||||
scheduled_at=scheduled_at,
|
||||
)
|
||||
|
||||
action = "saved as draft" if draft else "sent"
|
||||
if scheduled_at:
|
||||
action = f"scheduled for {scheduled_at}"
|
||||
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"SMS {action} successfully", "sms": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "provider" in error_msg.lower():
|
||||
error_msg += " (Hint: Make sure you have configured an SMS provider in Appwrite)"
|
||||
return json.dumps({"success": False, "error": error_msg}, indent=2)
|
||||
|
||||
async def send_push(
|
||||
client: AppwriteClient,
|
||||
message_id: str,
|
||||
title: str,
|
||||
body: str,
|
||||
topics: list[str] | None = None,
|
||||
users: list[str] | None = None,
|
||||
targets: list[str] | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
action: str | None = None,
|
||||
image: str | None = None,
|
||||
icon: str | None = None,
|
||||
sound: str | None = None,
|
||||
badge: int | None = None,
|
||||
draft: bool = False,
|
||||
scheduled_at: str | None = None,
|
||||
) -> str:
|
||||
"""Send push notification."""
|
||||
try:
|
||||
result = await client.create_push(
|
||||
message_id=message_id,
|
||||
title=title,
|
||||
body=body,
|
||||
topics=topics,
|
||||
users=users,
|
||||
targets=targets,
|
||||
data_payload=data,
|
||||
action=action,
|
||||
image=image,
|
||||
icon=icon,
|
||||
sound=sound,
|
||||
badge=badge,
|
||||
draft=draft,
|
||||
scheduled_at=scheduled_at,
|
||||
)
|
||||
|
||||
action_text = "saved as draft" if draft else "sent"
|
||||
if scheduled_at:
|
||||
action_text = f"scheduled for {scheduled_at}"
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Push notification {action_text} successfully",
|
||||
"push": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "provider" in error_msg.lower():
|
||||
error_msg += (
|
||||
" (Hint: Make sure you have configured a push provider (FCM/APNs) in Appwrite)"
|
||||
)
|
||||
return json.dumps({"success": False, "error": error_msg}, indent=2)
|
||||
|
||||
async def delete_message(client: AppwriteClient, message_id: str) -> str:
|
||||
"""Delete message."""
|
||||
try:
|
||||
await client.delete_message(message_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Message '{message_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
685
plugins/appwrite/handlers/storage.py
Normal file
685
plugins/appwrite/handlers/storage.py
Normal file
@@ -0,0 +1,685 @@
|
||||
"""
|
||||
Storage Handler - manages Appwrite storage buckets and files
|
||||
|
||||
Phase I.3: 14 tools
|
||||
- Buckets: 5 (list, get, create, update, delete)
|
||||
- Files: 9 (list, get, create, update, delete, download, preview, view, get_url)
|
||||
"""
|
||||
|
||||
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 (14 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# BUCKETS (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_buckets",
|
||||
"method_name": "list_buckets",
|
||||
"description": "List all storage buckets.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter buckets",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_bucket",
|
||||
"method_name": "get_bucket",
|
||||
"description": "Get bucket details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"bucket_id": {"type": "string", "description": "Bucket ID"}},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_bucket",
|
||||
"method_name": "create_bucket",
|
||||
"description": "Create a new storage bucket.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {
|
||||
"type": "string",
|
||||
"description": "Unique bucket ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"name": {"type": "string", "description": "Bucket name"},
|
||||
"permissions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Bucket permissions",
|
||||
},
|
||||
"file_security": {
|
||||
"type": "boolean",
|
||||
"description": "Enable file-level security",
|
||||
"default": True,
|
||||
},
|
||||
"enabled": {"type": "boolean", "description": "Enable bucket", "default": True},
|
||||
"maximum_file_size": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Maximum file size in bytes",
|
||||
},
|
||||
"allowed_file_extensions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Allowed file extensions (e.g., ['jpg', 'png', 'pdf'])",
|
||||
},
|
||||
"compression": {
|
||||
"type": "string",
|
||||
"enum": ["none", "gzip", "zstd"],
|
||||
"description": "Compression algorithm",
|
||||
"default": "none",
|
||||
},
|
||||
"encryption": {
|
||||
"type": "boolean",
|
||||
"description": "Enable encryption",
|
||||
"default": True,
|
||||
},
|
||||
"antivirus": {
|
||||
"type": "boolean",
|
||||
"description": "Enable antivirus scanning",
|
||||
"default": True,
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_bucket",
|
||||
"method_name": "update_bucket",
|
||||
"description": "Update bucket settings.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"name": {"type": "string", "description": "Bucket name"},
|
||||
"permissions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "New permissions",
|
||||
},
|
||||
"enabled": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable/disable bucket",
|
||||
},
|
||||
"maximum_file_size": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Maximum file size in bytes",
|
||||
},
|
||||
"allowed_file_extensions": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Allowed extensions",
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_bucket",
|
||||
"method_name": "delete_bucket",
|
||||
"description": "Delete a storage bucket. All files in the bucket will be deleted.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID to delete"}
|
||||
},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# FILES (9)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_files",
|
||||
"method_name": "list_files",
|
||||
"description": "List files in a bucket.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter files by name",
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_file",
|
||||
"method_name": "get_file",
|
||||
"description": "Get file metadata (not content).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_id": {"type": "string", "description": "File ID"},
|
||||
},
|
||||
"required": ["bucket_id", "file_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "delete_file",
|
||||
"method_name": "delete_file",
|
||||
"description": "Delete a file from storage.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_id": {"type": "string", "description": "File ID to delete"},
|
||||
},
|
||||
"required": ["bucket_id", "file_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "download_file",
|
||||
"method_name": "download_file",
|
||||
"description": "Download file content. Returns base64 encoded data.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_id": {"type": "string", "description": "File ID"},
|
||||
},
|
||||
"required": ["bucket_id", "file_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_file_preview",
|
||||
"method_name": "get_file_preview",
|
||||
"description": "Get image preview with transformations (resize, crop, etc.). Only works for image files.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_id": {"type": "string", "description": "File ID"},
|
||||
"width": {
|
||||
"anyOf": [
|
||||
{"type": "integer", "minimum": 0, "maximum": 4000},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Width in pixels (0-4000)",
|
||||
},
|
||||
"height": {
|
||||
"anyOf": [
|
||||
{"type": "integer", "minimum": 0, "maximum": 4000},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Height in pixels (0-4000)",
|
||||
},
|
||||
"gravity": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"center",
|
||||
"top",
|
||||
"top-left",
|
||||
"top-right",
|
||||
"left",
|
||||
"right",
|
||||
"bottom",
|
||||
"bottom-left",
|
||||
"bottom-right",
|
||||
],
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Crop gravity",
|
||||
},
|
||||
"quality": {
|
||||
"anyOf": [
|
||||
{"type": "integer", "minimum": 0, "maximum": 100},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Image quality (0-100)",
|
||||
},
|
||||
"border_width": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Border width in pixels",
|
||||
},
|
||||
"border_color": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Border color (hex without #)",
|
||||
},
|
||||
"border_radius": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Border radius for rounded corners",
|
||||
},
|
||||
"rotation": {
|
||||
"anyOf": [
|
||||
{"type": "integer", "minimum": 0, "maximum": 360},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Rotation angle (0-360)",
|
||||
},
|
||||
"background": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Background color (hex without #)",
|
||||
},
|
||||
"output": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["jpeg", "jpg", "png", "gif", "webp", "avif"],
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Output format",
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id", "file_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_file_view",
|
||||
"method_name": "get_file_view",
|
||||
"description": "Get file content for viewing in browser. Returns base64 encoded data.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_id": {"type": "string", "description": "File ID"},
|
||||
},
|
||||
"required": ["bucket_id", "file_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_file_url",
|
||||
"method_name": "get_file_url",
|
||||
"description": "Get the public URL for a file (if bucket is public).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_id": {"type": "string", "description": "File ID"},
|
||||
"url_type": {
|
||||
"type": "string",
|
||||
"enum": ["view", "download", "preview"],
|
||||
"description": "Type of URL to generate",
|
||||
"default": "view",
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id", "file_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "bulk_delete_files",
|
||||
"method_name": "bulk_delete_files",
|
||||
"description": "Delete multiple files from a bucket.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bucket_id": {"type": "string", "description": "Bucket ID"},
|
||||
"file_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Array of file IDs to delete",
|
||||
},
|
||||
},
|
||||
"required": ["bucket_id", "file_ids"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "get_bucket_stats",
|
||||
"method_name": "get_bucket_stats",
|
||||
"description": "Get storage statistics for a bucket (file count and total size).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"bucket_id": {"type": "string", "description": "Bucket ID"}},
|
||||
"required": ["bucket_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_buckets(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all buckets."""
|
||||
try:
|
||||
result = await client.list_buckets(queries=queries, search=search)
|
||||
buckets = result.get("buckets", [])
|
||||
|
||||
response = {"success": True, "total": result.get("total", len(buckets)), "buckets": buckets}
|
||||
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_bucket(client: AppwriteClient, bucket_id: str) -> str:
|
||||
"""Get bucket by ID."""
|
||||
try:
|
||||
result = await client.get_bucket(bucket_id)
|
||||
return json.dumps({"success": True, "bucket": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_bucket(
|
||||
client: AppwriteClient,
|
||||
bucket_id: str,
|
||||
name: str,
|
||||
permissions: list[str] | None = None,
|
||||
file_security: bool = True,
|
||||
enabled: bool = True,
|
||||
maximum_file_size: int | None = None,
|
||||
allowed_file_extensions: list[str] | None = None,
|
||||
compression: str = "none",
|
||||
encryption: bool = True,
|
||||
antivirus: bool = True,
|
||||
) -> str:
|
||||
"""Create a new bucket."""
|
||||
try:
|
||||
result = await client.create_bucket(
|
||||
bucket_id=bucket_id,
|
||||
name=name,
|
||||
permissions=permissions,
|
||||
file_security=file_security,
|
||||
enabled=enabled,
|
||||
maximum_file_size=maximum_file_size,
|
||||
allowed_file_extensions=allowed_file_extensions,
|
||||
compression=compression,
|
||||
encryption=encryption,
|
||||
antivirus=antivirus,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Bucket '{name}' created successfully", "bucket": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_bucket(
|
||||
client: AppwriteClient,
|
||||
bucket_id: str,
|
||||
name: str,
|
||||
permissions: list[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
maximum_file_size: int | None = None,
|
||||
allowed_file_extensions: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Update bucket."""
|
||||
try:
|
||||
result = await client.update_bucket(
|
||||
bucket_id=bucket_id,
|
||||
name=name,
|
||||
permissions=permissions,
|
||||
enabled=enabled,
|
||||
maximum_file_size=maximum_file_size,
|
||||
allowed_file_extensions=allowed_file_extensions,
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Bucket updated successfully", "bucket": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_bucket(client: AppwriteClient, bucket_id: str) -> str:
|
||||
"""Delete bucket."""
|
||||
try:
|
||||
await client.delete_bucket(bucket_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Bucket '{bucket_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_files(
|
||||
client: AppwriteClient,
|
||||
bucket_id: str,
|
||||
queries: list[str] | None = None,
|
||||
search: str | None = None,
|
||||
) -> str:
|
||||
"""List files in bucket."""
|
||||
try:
|
||||
result = await client.list_files(bucket_id=bucket_id, queries=queries, search=search)
|
||||
files = result.get("files", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"bucket_id": bucket_id,
|
||||
"total": result.get("total", len(files)),
|
||||
"files": files,
|
||||
}
|
||||
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_file(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
|
||||
"""Get file metadata."""
|
||||
try:
|
||||
result = await client.get_file(bucket_id, file_id)
|
||||
return json.dumps({"success": True, "file": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_file(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
|
||||
"""Delete file."""
|
||||
try:
|
||||
await client.delete_file(bucket_id, file_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"File '{file_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def download_file(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
|
||||
"""Download file content."""
|
||||
try:
|
||||
result = await client.get_file_download(bucket_id, file_id)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"file_id": file_id,
|
||||
"content_type": result.get("content_type"),
|
||||
"size": result.get("size"),
|
||||
"data_base64": result.get("data"),
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_file_preview(
|
||||
client: AppwriteClient,
|
||||
bucket_id: str,
|
||||
file_id: str,
|
||||
width: int | None = None,
|
||||
height: int | None = None,
|
||||
gravity: str | None = None,
|
||||
quality: int | None = None,
|
||||
border_width: int | None = None,
|
||||
border_color: str | None = None,
|
||||
border_radius: int | None = None,
|
||||
rotation: int | None = None,
|
||||
background: str | None = None,
|
||||
output: str | None = None,
|
||||
) -> str:
|
||||
"""Get file preview with transformations."""
|
||||
try:
|
||||
result = await client.get_file_preview(
|
||||
bucket_id=bucket_id,
|
||||
file_id=file_id,
|
||||
width=width,
|
||||
height=height,
|
||||
gravity=gravity,
|
||||
quality=quality,
|
||||
border_width=border_width,
|
||||
border_color=border_color,
|
||||
border_radius=border_radius,
|
||||
rotation=rotation,
|
||||
background=background,
|
||||
output=output,
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"file_id": file_id,
|
||||
"transformations": {
|
||||
"width": width,
|
||||
"height": height,
|
||||
"quality": quality,
|
||||
"output": output,
|
||||
},
|
||||
"content_type": result.get("content_type"),
|
||||
"size": result.get("size"),
|
||||
"data_base64": result.get("data"),
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_file_view(client: AppwriteClient, bucket_id: str, file_id: str) -> str:
|
||||
"""Get file for viewing."""
|
||||
try:
|
||||
result = await client.get_file_view(bucket_id, file_id)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"file_id": file_id,
|
||||
"content_type": result.get("content_type"),
|
||||
"size": result.get("size"),
|
||||
"data_base64": result.get("data"),
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_file_url(
|
||||
client: AppwriteClient, bucket_id: str, file_id: str, url_type: str = "view"
|
||||
) -> str:
|
||||
"""Get file URL."""
|
||||
try:
|
||||
base_url = client.base_url
|
||||
if url_type == "download":
|
||||
url = f"{base_url}/storage/buckets/{bucket_id}/files/{file_id}/download"
|
||||
elif url_type == "preview":
|
||||
url = f"{base_url}/storage/buckets/{bucket_id}/files/{file_id}/preview"
|
||||
else:
|
||||
url = f"{base_url}/storage/buckets/{bucket_id}/files/{file_id}/view"
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"file_id": file_id,
|
||||
"url_type": url_type,
|
||||
"url": url,
|
||||
"note": "URL requires authentication unless bucket is public",
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def bulk_delete_files(client: AppwriteClient, bucket_id: str, file_ids: list[str]) -> str:
|
||||
"""Delete multiple files."""
|
||||
try:
|
||||
results = []
|
||||
errors = []
|
||||
|
||||
for file_id in file_ids:
|
||||
try:
|
||||
await client.delete_file(bucket_id, file_id)
|
||||
results.append({"id": file_id, "success": True})
|
||||
except Exception as e:
|
||||
errors.append({"file_id": file_id, "error": str(e)})
|
||||
|
||||
response = {
|
||||
"success": len(errors) == 0,
|
||||
"deleted": len(results),
|
||||
"failed": len(errors),
|
||||
"results": results,
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
|
||||
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_bucket_stats(client: AppwriteClient, bucket_id: str) -> str:
|
||||
"""Get bucket statistics."""
|
||||
try:
|
||||
# Get files without query params to avoid syntax errors
|
||||
# Appwrite returns first 25 files by default
|
||||
result = await client.list_files(bucket_id=bucket_id)
|
||||
files = result.get("files", [])
|
||||
total = result.get("total", len(files))
|
||||
|
||||
total_size = sum(f.get("sizeOriginal", 0) for f in files)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"bucket_id": bucket_id,
|
||||
"stats": {
|
||||
"file_count": total,
|
||||
"total_size_bytes": total_size,
|
||||
"total_size_mb": round(total_size / (1024 * 1024), 2),
|
||||
"sample_count": len(files),
|
||||
},
|
||||
"note": (
|
||||
f"Size calculated from {len(files)} sampled files"
|
||||
if total > len(files)
|
||||
else None
|
||||
),
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
298
plugins/appwrite/handlers/system.py
Normal file
298
plugins/appwrite/handlers/system.py
Normal file
@@ -0,0 +1,298 @@
|
||||
"""
|
||||
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)
|
||||
374
plugins/appwrite/handlers/teams.py
Normal file
374
plugins/appwrite/handlers/teams.py
Normal file
@@ -0,0 +1,374 @@
|
||||
"""
|
||||
Teams Handler - manages Appwrite teams and memberships
|
||||
|
||||
Phase I.2: 10 tools
|
||||
- Teams: 5 (list, get, create, update, delete)
|
||||
- Memberships: 5 (list_memberships, create_membership, update_membership, delete_membership, get_membership_status)
|
||||
"""
|
||||
|
||||
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 (10 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# TEAMS (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_teams",
|
||||
"method_name": "list_teams",
|
||||
"description": "List all teams in the project.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter teams by name",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_team",
|
||||
"method_name": "get_team",
|
||||
"description": "Get team details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"team_id": {"type": "string", "description": "Team ID"}},
|
||||
"required": ["team_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_team",
|
||||
"method_name": "create_team",
|
||||
"description": "Create a new team. Use 'unique()' for auto-generated team ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {
|
||||
"type": "string",
|
||||
"description": "Unique team ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"name": {"type": "string", "description": "Team name"},
|
||||
"roles": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Array of roles available in this team (e.g., ['owner', 'editor', 'viewer'])",
|
||||
},
|
||||
},
|
||||
"required": ["team_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_team",
|
||||
"method_name": "update_team",
|
||||
"description": "Update team name.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {"type": "string", "description": "Team ID"},
|
||||
"name": {"type": "string", "description": "New team name"},
|
||||
},
|
||||
"required": ["team_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_team",
|
||||
"method_name": "delete_team",
|
||||
"description": "Delete a team and all its memberships. This action is irreversible.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"team_id": {"type": "string", "description": "Team ID to delete"}},
|
||||
"required": ["team_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# MEMBERSHIPS (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_team_memberships",
|
||||
"method_name": "list_team_memberships",
|
||||
"description": "List all memberships (members) of a team.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {"type": "string", "description": "Team ID"},
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings for filtering",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter members",
|
||||
},
|
||||
},
|
||||
"required": ["team_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_team_membership",
|
||||
"method_name": "create_team_membership",
|
||||
"description": "Invite a user to join a team. Can invite by email, phone, or existing user ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {"type": "string", "description": "Team ID"},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Roles to assign to the member",
|
||||
},
|
||||
"email": {
|
||||
"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}],
|
||||
"description": "Email address to invite",
|
||||
},
|
||||
"user_id": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Existing user ID to add",
|
||||
},
|
||||
"phone": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Phone number to invite",
|
||||
},
|
||||
"url": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Redirect URL after accepting invitation",
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Name of the invitee",
|
||||
},
|
||||
},
|
||||
"required": ["team_id", "roles"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_membership",
|
||||
"method_name": "update_membership",
|
||||
"description": "Update team membership roles.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {"type": "string", "description": "Team ID"},
|
||||
"membership_id": {"type": "string", "description": "Membership ID"},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "New roles for the member",
|
||||
},
|
||||
},
|
||||
"required": ["team_id", "membership_id", "roles"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_membership",
|
||||
"method_name": "delete_membership",
|
||||
"description": "Remove a member from a team.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {"type": "string", "description": "Team ID"},
|
||||
"membership_id": {"type": "string", "description": "Membership ID to remove"},
|
||||
},
|
||||
"required": ["team_id", "membership_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "get_team_prefs",
|
||||
"method_name": "get_team_prefs",
|
||||
"description": "Get team preferences/settings.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"team_id": {"type": "string", "description": "Team ID"}},
|
||||
"required": ["team_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_teams(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all teams."""
|
||||
try:
|
||||
result = await client.list_teams(queries=queries, search=search)
|
||||
teams = result.get("teams", [])
|
||||
|
||||
response = {"success": True, "total": result.get("total", len(teams)), "teams": teams}
|
||||
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_team(client: AppwriteClient, team_id: str) -> str:
|
||||
"""Get team by ID."""
|
||||
try:
|
||||
result = await client.get_team(team_id)
|
||||
return json.dumps({"success": True, "team": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_team(
|
||||
client: AppwriteClient, team_id: str, name: str, roles: list[str] | None = None
|
||||
) -> str:
|
||||
"""Create a new team."""
|
||||
try:
|
||||
result = await client.create_team(team_id=team_id, name=name, roles=roles)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Team '{name}' created successfully", "team": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_team(client: AppwriteClient, team_id: str, name: str) -> str:
|
||||
"""Update team name."""
|
||||
try:
|
||||
result = await client.update_team(team_id=team_id, name=name)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Team updated successfully", "team": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_team(client: AppwriteClient, team_id: str) -> str:
|
||||
"""Delete team."""
|
||||
try:
|
||||
await client.delete_team(team_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Team '{team_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_team_memberships(
|
||||
client: AppwriteClient,
|
||||
team_id: str,
|
||||
queries: list[str] | None = None,
|
||||
search: str | None = None,
|
||||
) -> str:
|
||||
"""List team memberships."""
|
||||
try:
|
||||
result = await client.list_team_memberships(team_id=team_id, queries=queries, search=search)
|
||||
memberships = result.get("memberships", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"team_id": team_id,
|
||||
"total": result.get("total", len(memberships)),
|
||||
"memberships": memberships,
|
||||
}
|
||||
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 create_team_membership(
|
||||
client: AppwriteClient,
|
||||
team_id: str,
|
||||
roles: list[str],
|
||||
email: str | None = None,
|
||||
user_id: str | None = None,
|
||||
phone: str | None = None,
|
||||
url: str | None = None,
|
||||
name: str | None = None,
|
||||
) -> str:
|
||||
"""Create team membership (invite)."""
|
||||
try:
|
||||
result = await client.create_team_membership(
|
||||
team_id=team_id,
|
||||
roles=roles,
|
||||
email=email,
|
||||
user_id=user_id,
|
||||
phone=phone,
|
||||
url=url,
|
||||
name=name,
|
||||
)
|
||||
target = email or user_id or phone or "user"
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Membership created/invitation sent to {target}",
|
||||
"membership": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_membership(
|
||||
client: AppwriteClient, team_id: str, membership_id: str, roles: list[str]
|
||||
) -> str:
|
||||
"""Update membership roles."""
|
||||
try:
|
||||
result = await client.update_membership(
|
||||
team_id=team_id, membership_id=membership_id, roles=roles
|
||||
)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Membership roles updated to: {roles}",
|
||||
"membership": result,
|
||||
},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_membership(client: AppwriteClient, team_id: str, membership_id: str) -> str:
|
||||
"""Delete membership."""
|
||||
try:
|
||||
await client.delete_membership(team_id, membership_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Membership '{membership_id}' removed from team"},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_team_prefs(client: AppwriteClient, team_id: str) -> str:
|
||||
"""Get team preferences (placeholder - requires team prefs endpoint)."""
|
||||
try:
|
||||
# Note: Team prefs endpoint may vary by Appwrite version
|
||||
# This is a simplified version that returns team info
|
||||
result = await client.get_team(team_id)
|
||||
return json.dumps(
|
||||
{"success": True, "team_id": team_id, "prefs": result.get("prefs", {}), "team": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
384
plugins/appwrite/handlers/users.py
Normal file
384
plugins/appwrite/handlers/users.py
Normal file
@@ -0,0 +1,384 @@
|
||||
"""
|
||||
Users Handler - manages Appwrite user operations
|
||||
|
||||
Phase I.2: 12 tools
|
||||
- User CRUD: 5 (list, get, create, update, delete)
|
||||
- User Properties: 4 (update_email, update_phone, update_status, update_labels)
|
||||
- Sessions: 3 (list_sessions, delete_sessions, delete_session)
|
||||
"""
|
||||
|
||||
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 (12 tools)"""
|
||||
return [
|
||||
# =====================
|
||||
# USER CRUD (5)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_users",
|
||||
"method_name": "list_users",
|
||||
"description": "List all users in the project with optional filtering and search.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"queries": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "Query strings (e.g., 'limit(25)', 'offset(0)')",
|
||||
},
|
||||
"search": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search term to filter users by name or email",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_user",
|
||||
"method_name": "get_user",
|
||||
"description": "Get user details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"user_id": {"type": "string", "description": "User ID"}},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_user",
|
||||
"method_name": "create_user",
|
||||
"description": "Create a new user. Use 'unique()' for auto-generated user ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "Unique user ID. Use 'unique()' for auto-generation",
|
||||
},
|
||||
"email": {
|
||||
"anyOf": [{"type": "string", "format": "email"}, {"type": "null"}],
|
||||
"description": "User email address",
|
||||
},
|
||||
"phone": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "User phone number (E.164 format)",
|
||||
},
|
||||
"password": {
|
||||
"anyOf": [{"type": "string", "minLength": 8}, {"type": "null"}],
|
||||
"description": "User password (min 8 characters)",
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "User display name",
|
||||
},
|
||||
},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_user_name",
|
||||
"method_name": "update_user_name",
|
||||
"description": "Update user display name.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "description": "User ID"},
|
||||
"name": {"type": "string", "description": "New display name"},
|
||||
},
|
||||
"required": ["user_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_user",
|
||||
"method_name": "delete_user",
|
||||
"description": "Delete a user. This action is irreversible.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"user_id": {"type": "string", "description": "User ID to delete"}},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# USER PROPERTIES (4)
|
||||
# =====================
|
||||
{
|
||||
"name": "update_user_email",
|
||||
"method_name": "update_user_email",
|
||||
"description": "Update user email address.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "description": "User ID"},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "New email address",
|
||||
},
|
||||
},
|
||||
"required": ["user_id", "email"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_user_phone",
|
||||
"method_name": "update_user_phone",
|
||||
"description": "Update user phone number.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "description": "User ID"},
|
||||
"number": {
|
||||
"type": "string",
|
||||
"description": "New phone number (E.164 format, e.g., +14155552671)",
|
||||
},
|
||||
},
|
||||
"required": ["user_id", "number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_user_status",
|
||||
"method_name": "update_user_status",
|
||||
"description": "Enable or disable a user account.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "description": "User ID"},
|
||||
"status": {
|
||||
"type": "boolean",
|
||||
"description": "True to enable, False to disable",
|
||||
},
|
||||
},
|
||||
"required": ["user_id", "status"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "update_user_labels",
|
||||
"method_name": "update_user_labels",
|
||||
"description": "Set user labels for permission management. Labels can be used in permission strings like 'label:admin'.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "description": "User ID"},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Array of label strings (replaces existing labels)",
|
||||
},
|
||||
},
|
||||
"required": ["user_id", "labels"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# =====================
|
||||
# SESSIONS (3)
|
||||
# =====================
|
||||
{
|
||||
"name": "list_user_sessions",
|
||||
"method_name": "list_user_sessions",
|
||||
"description": "List all active sessions for a user.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"user_id": {"type": "string", "description": "User ID"}},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "delete_user_sessions",
|
||||
"method_name": "delete_user_sessions",
|
||||
"description": "Delete all sessions for a user (force logout everywhere).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"user_id": {"type": "string", "description": "User ID"}},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_user_session",
|
||||
"method_name": "delete_user_session",
|
||||
"description": "Delete a specific user session.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "description": "User ID"},
|
||||
"session_id": {"type": "string", "description": "Session ID to delete"},
|
||||
},
|
||||
"required": ["user_id", "session_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
]
|
||||
|
||||
# =====================
|
||||
# HANDLER FUNCTIONS
|
||||
# =====================
|
||||
|
||||
async def list_users(
|
||||
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
||||
) -> str:
|
||||
"""List all users."""
|
||||
try:
|
||||
result = await client.list_users(queries=queries, search=search)
|
||||
users = result.get("users", [])
|
||||
|
||||
response = {"success": True, "total": result.get("total", len(users)), "users": users}
|
||||
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_user(client: AppwriteClient, user_id: str) -> str:
|
||||
"""Get user by ID."""
|
||||
try:
|
||||
result = await client.get_user(user_id)
|
||||
return json.dumps({"success": True, "user": result}, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_user(
|
||||
client: AppwriteClient,
|
||||
user_id: str,
|
||||
email: str | None = None,
|
||||
phone: str | None = None,
|
||||
password: str | None = None,
|
||||
name: str | None = None,
|
||||
) -> str:
|
||||
"""Create a new user."""
|
||||
try:
|
||||
result = await client.create_user(
|
||||
user_id=user_id, email=email, phone=phone, password=password, name=name
|
||||
)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "User created successfully", "user": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_user_name(client: AppwriteClient, user_id: str, name: str) -> str:
|
||||
"""Update user name."""
|
||||
try:
|
||||
result = await client.update_user_name(user_id=user_id, name=name)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "User name updated successfully", "user": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_user(client: AppwriteClient, user_id: str) -> str:
|
||||
"""Delete user."""
|
||||
try:
|
||||
await client.delete_user(user_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"User '{user_id}' deleted successfully"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_user_email(client: AppwriteClient, user_id: str, email: str) -> str:
|
||||
"""Update user email."""
|
||||
try:
|
||||
result = await client.update_user_email(user_id=user_id, email=email)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "User email updated successfully", "user": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_user_phone(client: AppwriteClient, user_id: str, number: str) -> str:
|
||||
"""Update user phone."""
|
||||
try:
|
||||
result = await client.update_user_phone(user_id=user_id, number=number)
|
||||
return json.dumps(
|
||||
{"success": True, "message": "User phone updated successfully", "user": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_user_status(client: AppwriteClient, user_id: str, status: bool) -> str:
|
||||
"""Update user status (enable/disable)."""
|
||||
try:
|
||||
result = await client.update_user_status(user_id=user_id, status=status)
|
||||
action = "enabled" if status else "disabled"
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"User {action} successfully", "user": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_user_labels(client: AppwriteClient, user_id: str, labels: list[str]) -> str:
|
||||
"""Update user labels."""
|
||||
try:
|
||||
result = await client.update_user_labels(user_id=user_id, labels=labels)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"User labels updated: {labels}", "user": result},
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def list_user_sessions(client: AppwriteClient, user_id: str) -> str:
|
||||
"""List user sessions."""
|
||||
try:
|
||||
result = await client.list_user_sessions(user_id)
|
||||
sessions = result.get("sessions", [])
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"user_id": user_id,
|
||||
"total": result.get("total", len(sessions)),
|
||||
"sessions": sessions,
|
||||
}
|
||||
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 delete_user_sessions(client: AppwriteClient, user_id: str) -> str:
|
||||
"""Delete all user sessions."""
|
||||
try:
|
||||
await client.delete_user_sessions(user_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"All sessions for user '{user_id}' deleted"}, indent=2
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_user_session(client: AppwriteClient, user_id: str, session_id: str) -> str:
|
||||
"""Delete a specific session."""
|
||||
try:
|
||||
await client.delete_user_session(user_id, session_id)
|
||||
return json.dumps({"success": True, "message": f"Session '{session_id}' deleted"}, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
166
plugins/appwrite/plugin.py
Normal file
166
plugins/appwrite/plugin.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Appwrite Plugin - Self-Hosted Backend-as-a-Service Management
|
||||
|
||||
Complete Appwrite Self-Hosted management through REST APIs.
|
||||
Provides tools for Databases, Documents, Users, Teams, Storage,
|
||||
Functions, Messaging, and System operations.
|
||||
|
||||
For Self-Hosted instances deployed on Coolify.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from plugins.appwrite import handlers
|
||||
from plugins.appwrite.client import AppwriteClient
|
||||
from plugins.base import BasePlugin
|
||||
|
||||
class AppwritePlugin(BasePlugin):
|
||||
"""
|
||||
Appwrite Self-Hosted Plugin - Complete backend management.
|
||||
|
||||
Provides comprehensive Appwrite management capabilities including:
|
||||
- Database operations (Databases, Collections, Attributes, Indexes)
|
||||
- Document operations (CRUD, Bulk ops, Queries, Search)
|
||||
- User management (CRUD, Labels, Sessions, Status)
|
||||
- Team management (Teams, Memberships, Roles)
|
||||
- Storage operations (Buckets, Files, Image transformation)
|
||||
- Functions (Functions, Deployments, Executions)
|
||||
- Messaging (Topics, Subscribers, Email/SMS/Push messages)
|
||||
- System operations (Health checks, Avatars)
|
||||
|
||||
Phase I.1: Database (18) + Documents (12) + System (8) = 38 tools
|
||||
Phase I.2: Users (12) + Teams (10) = 22 tools
|
||||
Phase I.3: Storage (14) = 14 tools
|
||||
Phase I.4: Functions (14) + Messaging (12) = 26 tools
|
||||
|
||||
Total: 100 tools - Complete!
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_plugin_name() -> str:
|
||||
"""Return plugin type identifier"""
|
||||
return "appwrite"
|
||||
|
||||
@staticmethod
|
||||
def get_required_config_keys() -> list[str]:
|
||||
"""Return required configuration keys"""
|
||||
return ["url", "project_id", "api_key"]
|
||||
|
||||
def __init__(self, config: dict[str, Any], project_id: str | None = None):
|
||||
"""
|
||||
Initialize Appwrite plugin with client.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary containing:
|
||||
- url: Appwrite instance URL (e.g., https://appwrite.example.com/v1)
|
||||
- project_id: Appwrite project ID
|
||||
- api_key: API key with appropriate scopes
|
||||
project_id: Optional MCP project ID (auto-generated if not provided)
|
||||
"""
|
||||
super().__init__(config, project_id=project_id)
|
||||
|
||||
# Create Appwrite API client
|
||||
self.client = AppwriteClient(
|
||||
base_url=config["url"], project_id=config["project_id"], api_key=config["api_key"]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""
|
||||
Return all tool specifications for ToolGenerator.
|
||||
|
||||
This method is called by ToolGenerator to create unified tools
|
||||
with site parameter routing.
|
||||
|
||||
Returns:
|
||||
List of tool specification dictionaries
|
||||
"""
|
||||
specs = []
|
||||
|
||||
# Phase I.1: Core (38 tools)
|
||||
specs.extend(handlers.databases.get_tool_specifications()) # 18 tools
|
||||
specs.extend(handlers.documents.get_tool_specifications()) # 12 tools
|
||||
specs.extend(handlers.system.get_tool_specifications()) # 8 tools
|
||||
|
||||
# Phase I.2: Auth & Teams (22 tools)
|
||||
specs.extend(handlers.users.get_tool_specifications()) # 12 tools
|
||||
specs.extend(handlers.teams.get_tool_specifications()) # 10 tools
|
||||
|
||||
# Phase I.3: Storage (14 tools)
|
||||
specs.extend(handlers.storage.get_tool_specifications()) # 14 tools
|
||||
|
||||
# Phase I.4: Functions & Messaging (26 tools)
|
||||
specs.extend(handlers.functions.get_tool_specifications()) # 14 tools
|
||||
specs.extend(handlers.messaging.get_tool_specifications()) # 12 tools
|
||||
|
||||
return specs
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""
|
||||
Dynamically delegate method calls to appropriate handlers.
|
||||
|
||||
This allows ToolGenerator to call methods like plugin.list_databases()
|
||||
without explicitly defining each method.
|
||||
|
||||
Args:
|
||||
name: Method name being called
|
||||
|
||||
Returns:
|
||||
Handler function from the appropriate handler module
|
||||
"""
|
||||
# Try to find the method in handler modules
|
||||
handler_modules = [
|
||||
# Phase I.1: Core
|
||||
handlers.databases,
|
||||
handlers.documents,
|
||||
handlers.system,
|
||||
# Phase I.2: Auth & Teams
|
||||
handlers.users,
|
||||
handlers.teams,
|
||||
# Phase I.3: Storage
|
||||
handlers.storage,
|
||||
# Phase I.4: Functions & Messaging
|
||||
handlers.functions,
|
||||
handlers.messaging,
|
||||
]
|
||||
|
||||
for handler_module in handler_modules:
|
||||
if hasattr(handler_module, name):
|
||||
func = getattr(handler_module, name)
|
||||
|
||||
# Create wrapper that passes self.client
|
||||
async def wrapper(_func=func, **kwargs):
|
||||
return await _func(self.client, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
# Method not found in any handler
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
async def check_health(self) -> dict[str, Any]:
|
||||
"""
|
||||
Check if Appwrite instance is accessible (internal use).
|
||||
|
||||
Note: This is named check_health to avoid shadowing the handler's
|
||||
health_check function which is exposed as an MCP tool.
|
||||
|
||||
Returns:
|
||||
Dict containing health check result
|
||||
"""
|
||||
try:
|
||||
result = await self.client.health_check()
|
||||
is_healthy = result.get("status") == "pass"
|
||||
return {
|
||||
"healthy": is_healthy,
|
||||
"message": f"Appwrite instance at {self.client.base_url} is {'accessible' if is_healthy else 'not accessible'}",
|
||||
}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "message": f"Appwrite health check failed: {str(e)}"}
|
||||
|
||||
async def health_check(self, **kwargs) -> str:
|
||||
"""
|
||||
Override BasePlugin.health_check to use handler function.
|
||||
|
||||
This ensures the MCP tool returns a JSON string, not a Dict.
|
||||
"""
|
||||
return await handlers.system.health_check(self.client)
|
||||
Reference in New Issue
Block a user