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>
1336 lines
44 KiB
Python
1336 lines
44 KiB
Python
"""
|
|
Directus REST API Client (Self-Hosted)
|
|
|
|
Handles all HTTP communication with Directus Self-Hosted APIs.
|
|
Uses static token authentication via Authorization: Bearer header.
|
|
|
|
APIs:
|
|
- Items (/items/) - CRUD for any collection
|
|
- Collections (/collections/) - Schema management
|
|
- Fields (/fields/) - Field definitions
|
|
- Relations (/relations/) - Relationships
|
|
- Files (/files/) - Asset management
|
|
- Folders (/folders/) - Folder management
|
|
- Users (/users/) - User management
|
|
- Roles (/roles/) - Role management
|
|
- Permissions (/permissions/) - Permission rules
|
|
- Policies (/policies/) - Access policies
|
|
- Flows (/flows/) - Automation flows
|
|
- Operations (/operations/) - Flow operations
|
|
- Webhooks (/webhooks/) - Webhook management
|
|
- Activity (/activity/) - Activity log
|
|
- Revisions (/revisions/) - Content revisions
|
|
- Versions (/versions/) - Content versions
|
|
- Comments (/comments/) - Item comments
|
|
- Dashboards (/dashboards/) - Insights dashboards
|
|
- Panels (/panels/) - Dashboard panels
|
|
- Settings (/settings/) - System settings
|
|
- Server (/server/) - Server info & health
|
|
- Schema (/schema/) - Schema management
|
|
- Presets (/presets/) - User presets
|
|
- Shares (/shares/) - Content shares
|
|
- Notifications (/notifications/) - User notifications
|
|
- Translations (/translations/) - Content translations
|
|
"""
|
|
|
|
import base64
|
|
import json
|
|
import logging
|
|
from typing import Any
|
|
|
|
import aiohttp
|
|
|
|
def _ensure_list(value: Any) -> list[str]:
|
|
"""Ensure value is a list. If string, wrap in list."""
|
|
if value is None:
|
|
return []
|
|
if isinstance(value, list):
|
|
return value
|
|
if isinstance(value, str):
|
|
return [value]
|
|
return [str(value)]
|
|
|
|
class DirectusClient:
|
|
"""
|
|
Directus Self-Hosted API client.
|
|
|
|
Uses static token authentication for server-to-server communication.
|
|
All requests include Authorization: Bearer {token} header.
|
|
"""
|
|
|
|
def __init__(self, base_url: str, token: str):
|
|
"""
|
|
Initialize Directus API client.
|
|
|
|
Args:
|
|
base_url: Directus instance URL (e.g., https://directus.example.com)
|
|
token: Static admin token
|
|
"""
|
|
self.base_url = base_url.rstrip("/")
|
|
self.token = token
|
|
|
|
# Initialize logger
|
|
self.logger = logging.getLogger(f"DirectusClient.{base_url}")
|
|
|
|
def _get_headers(self, additional_headers: dict | None = None) -> dict[str, str]:
|
|
"""
|
|
Get request headers with token authentication.
|
|
|
|
Args:
|
|
additional_headers: Additional headers to include
|
|
|
|
Returns:
|
|
Dict: Headers with authentication
|
|
"""
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
"Authorization": f"Bearer {self.token}",
|
|
}
|
|
|
|
if additional_headers:
|
|
headers.update(additional_headers)
|
|
|
|
return headers
|
|
|
|
async def request(
|
|
self,
|
|
method: str,
|
|
endpoint: str,
|
|
params: dict | None = None,
|
|
json_data: dict | list | None = None,
|
|
data: bytes | None = None,
|
|
headers_override: dict | None = None,
|
|
) -> Any:
|
|
"""
|
|
Make authenticated request to Directus API.
|
|
|
|
Args:
|
|
method: HTTP method
|
|
endpoint: API endpoint (with leading /)
|
|
params: Query parameters
|
|
json_data: JSON body data
|
|
data: Raw binary data (for file uploads)
|
|
headers_override: Override/add headers
|
|
|
|
Returns:
|
|
API response
|
|
|
|
Raises:
|
|
Exception: On API errors
|
|
"""
|
|
url = f"{self.base_url}{endpoint}"
|
|
|
|
headers = self._get_headers(headers_override)
|
|
|
|
# Remove Content-Type for binary data (multipart)
|
|
if data is not None:
|
|
headers.pop("Content-Type", None)
|
|
|
|
# Clean params - remove None values
|
|
if params:
|
|
params = {k: v for k, v in params.items() if v is not None}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
kwargs = {
|
|
"method": method,
|
|
"url": url,
|
|
"headers": headers,
|
|
}
|
|
|
|
if params:
|
|
kwargs["params"] = params
|
|
if json_data is not None:
|
|
kwargs["json"] = json_data
|
|
if data:
|
|
kwargs["data"] = data
|
|
|
|
async with session.request(**kwargs) as response:
|
|
self.logger.debug(f"Response status: {response.status}")
|
|
|
|
# Handle 204 No Content
|
|
if response.status == 204:
|
|
return {"success": True}
|
|
|
|
# Handle binary responses (file downloads)
|
|
content_type = response.headers.get("Content-Type", "")
|
|
if "application/json" not in content_type and response.status < 400:
|
|
# Return binary data as base64
|
|
binary_data = await response.read()
|
|
return {
|
|
"data": base64.b64encode(binary_data).decode(),
|
|
"content_type": content_type,
|
|
"size": len(binary_data),
|
|
}
|
|
|
|
# Parse JSON response
|
|
try:
|
|
response_data = await response.json()
|
|
except Exception:
|
|
response_text = await response.text()
|
|
if response.status >= 400:
|
|
raise Exception(
|
|
f"Directus API error (status {response.status}): {response_text}"
|
|
)
|
|
return {"success": True, "message": response_text}
|
|
|
|
# Check for errors
|
|
if response.status >= 400:
|
|
error_msg = self._extract_error_message(response_data)
|
|
raise Exception(f"Directus API error (status {response.status}): {error_msg}")
|
|
|
|
return response_data
|
|
|
|
def _extract_error_message(self, response_data: Any) -> str:
|
|
"""Extract error message from Directus error response."""
|
|
if isinstance(response_data, dict):
|
|
errors = response_data.get("errors", [])
|
|
if errors and isinstance(errors, list) and len(errors) > 0:
|
|
first_error = errors[0]
|
|
if isinstance(first_error, dict):
|
|
return first_error.get("message", str(first_error))
|
|
if "message" in response_data:
|
|
return response_data["message"]
|
|
if "error" in response_data:
|
|
return response_data["error"]
|
|
return str(response_data)
|
|
|
|
# =====================
|
|
# ITEMS
|
|
# =====================
|
|
|
|
async def list_items(
|
|
self,
|
|
collection: str,
|
|
fields: list[str] | None = None,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
search: str | None = None,
|
|
deep: dict | None = None,
|
|
aggregate: dict | None = None,
|
|
) -> dict[str, Any]:
|
|
"""List items from a collection."""
|
|
params = {
|
|
"limit": limit,
|
|
"offset": offset,
|
|
}
|
|
if fields:
|
|
params["fields"] = ",".join(_ensure_list(fields))
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
if search:
|
|
params["search"] = search
|
|
if deep:
|
|
params["deep"] = json.dumps(deep)
|
|
if aggregate:
|
|
params["aggregate"] = json.dumps(aggregate)
|
|
|
|
return await self.request("GET", f"/items/{collection}", params=params)
|
|
|
|
async def get_item(
|
|
self, collection: str, id: str, fields: list[str] | None = None, deep: dict | None = None
|
|
) -> dict[str, Any]:
|
|
"""Get item by ID."""
|
|
params = {}
|
|
if fields:
|
|
params["fields"] = ",".join(_ensure_list(fields))
|
|
if deep:
|
|
params["deep"] = json.dumps(deep)
|
|
|
|
return await self.request("GET", f"/items/{collection}/{id}", params=params)
|
|
|
|
async def create_item(
|
|
self, collection: str, data: dict[str, Any], fields: list[str] | None = None
|
|
) -> dict[str, Any]:
|
|
"""Create a new item."""
|
|
params = {}
|
|
if fields:
|
|
params["fields"] = ",".join(_ensure_list(fields))
|
|
|
|
return await self.request("POST", f"/items/{collection}", params=params, json_data=data)
|
|
|
|
async def create_items(
|
|
self, collection: str, data: list[dict[str, Any]], fields: list[str] | None = None
|
|
) -> dict[str, Any]:
|
|
"""Create multiple items."""
|
|
params = {}
|
|
if fields:
|
|
params["fields"] = ",".join(_ensure_list(fields))
|
|
|
|
return await self.request("POST", f"/items/{collection}", params=params, json_data=data)
|
|
|
|
async def update_item(
|
|
self, collection: str, id: str, data: dict[str, Any], fields: list[str] | None = None
|
|
) -> dict[str, Any]:
|
|
"""Update an item."""
|
|
params = {}
|
|
if fields:
|
|
params["fields"] = ",".join(_ensure_list(fields))
|
|
|
|
return await self.request(
|
|
"PATCH", f"/items/{collection}/{id}", params=params, json_data=data
|
|
)
|
|
|
|
async def update_items(
|
|
self,
|
|
collection: str,
|
|
keys: list[str],
|
|
data: dict[str, Any],
|
|
fields: list[str] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Update multiple items."""
|
|
params = {}
|
|
if fields:
|
|
params["fields"] = ",".join(_ensure_list(fields))
|
|
|
|
body = {"keys": keys, "data": data}
|
|
return await self.request("PATCH", f"/items/{collection}", params=params, json_data=body)
|
|
|
|
async def delete_item(self, collection: str, id: str) -> dict[str, Any]:
|
|
"""Delete an item."""
|
|
return await self.request("DELETE", f"/items/{collection}/{id}")
|
|
|
|
async def delete_items(self, collection: str, keys: list[str]) -> dict[str, Any]:
|
|
"""Delete multiple items."""
|
|
return await self.request("DELETE", f"/items/{collection}", json_data=keys)
|
|
|
|
# =====================
|
|
# COLLECTIONS
|
|
# =====================
|
|
|
|
async def list_collections(self) -> dict[str, Any]:
|
|
"""List all collections."""
|
|
return await self.request("GET", "/collections")
|
|
|
|
async def get_collection(self, collection: str) -> dict[str, Any]:
|
|
"""Get collection by name."""
|
|
return await self.request("GET", f"/collections/{collection}")
|
|
|
|
async def create_collection(
|
|
self,
|
|
collection: str,
|
|
meta: dict | None = None,
|
|
schema: dict | None = None,
|
|
fields: list[dict] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new collection."""
|
|
data = {"collection": collection}
|
|
if meta:
|
|
data["meta"] = meta
|
|
if schema:
|
|
data["schema"] = schema
|
|
if fields:
|
|
data["fields"] = fields
|
|
|
|
return await self.request("POST", "/collections", json_data=data)
|
|
|
|
async def update_collection(self, collection: str, meta: dict) -> dict[str, Any]:
|
|
"""Update collection meta."""
|
|
return await self.request("PATCH", f"/collections/{collection}", json_data={"meta": meta})
|
|
|
|
async def delete_collection(self, collection: str) -> dict[str, Any]:
|
|
"""Delete a collection."""
|
|
return await self.request("DELETE", f"/collections/{collection}")
|
|
|
|
# =====================
|
|
# FIELDS
|
|
# =====================
|
|
|
|
async def list_fields(self, collection: str | None = None) -> dict[str, Any]:
|
|
"""List fields, optionally filtered by collection."""
|
|
if collection:
|
|
return await self.request("GET", f"/fields/{collection}")
|
|
return await self.request("GET", "/fields")
|
|
|
|
async def get_field(self, collection: str, field: str) -> dict[str, Any]:
|
|
"""Get field by name."""
|
|
return await self.request("GET", f"/fields/{collection}/{field}")
|
|
|
|
async def create_field(
|
|
self,
|
|
collection: str,
|
|
field: str,
|
|
type: str,
|
|
meta: dict | None = None,
|
|
schema: dict | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new field."""
|
|
data = {"field": field, "type": type}
|
|
if meta:
|
|
data["meta"] = meta
|
|
if schema:
|
|
data["schema"] = schema
|
|
|
|
return await self.request("POST", f"/fields/{collection}", json_data=data)
|
|
|
|
async def update_field(
|
|
self, collection: str, field: str, meta: dict | None = None, schema: dict | None = None
|
|
) -> dict[str, Any]:
|
|
"""Update a field."""
|
|
data = {}
|
|
if meta:
|
|
data["meta"] = meta
|
|
if schema:
|
|
data["schema"] = schema
|
|
|
|
return await self.request("PATCH", f"/fields/{collection}/{field}", json_data=data)
|
|
|
|
async def delete_field(self, collection: str, field: str) -> dict[str, Any]:
|
|
"""Delete a field."""
|
|
return await self.request("DELETE", f"/fields/{collection}/{field}")
|
|
|
|
# =====================
|
|
# RELATIONS
|
|
# =====================
|
|
|
|
async def list_relations(self, collection: str | None = None) -> dict[str, Any]:
|
|
"""List relations."""
|
|
if collection:
|
|
return await self.request("GET", f"/relations/{collection}")
|
|
return await self.request("GET", "/relations")
|
|
|
|
async def get_relation(self, collection: str, field: str) -> dict[str, Any]:
|
|
"""Get relation by collection and field."""
|
|
return await self.request("GET", f"/relations/{collection}/{field}")
|
|
|
|
async def create_relation(
|
|
self,
|
|
collection: str,
|
|
field: str,
|
|
related_collection: str,
|
|
meta: dict | None = None,
|
|
schema: dict | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new relation."""
|
|
data = {"collection": collection, "field": field, "related_collection": related_collection}
|
|
if meta:
|
|
data["meta"] = meta
|
|
if schema:
|
|
data["schema"] = schema
|
|
|
|
return await self.request("POST", "/relations", json_data=data)
|
|
|
|
async def update_relation(
|
|
self, collection: str, field: str, meta: dict | None = None, schema: dict | None = None
|
|
) -> dict[str, Any]:
|
|
"""Update a relation."""
|
|
data = {}
|
|
if meta:
|
|
data["meta"] = meta
|
|
if schema:
|
|
data["schema"] = schema
|
|
|
|
return await self.request("PATCH", f"/relations/{collection}/{field}", json_data=data)
|
|
|
|
async def delete_relation(self, collection: str, field: str) -> dict[str, Any]:
|
|
"""Delete a relation."""
|
|
return await self.request("DELETE", f"/relations/{collection}/{field}")
|
|
|
|
# =====================
|
|
# FILES
|
|
# =====================
|
|
|
|
async def list_files(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
search: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""List files."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
if search:
|
|
params["search"] = search
|
|
|
|
return await self.request("GET", "/files", params=params)
|
|
|
|
async def get_file(self, id: str) -> dict[str, Any]:
|
|
"""Get file by ID."""
|
|
return await self.request("GET", f"/files/{id}")
|
|
|
|
async def update_file(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update file metadata."""
|
|
return await self.request("PATCH", f"/files/{id}", json_data=data)
|
|
|
|
async def delete_file(self, id: str) -> dict[str, Any]:
|
|
"""Delete a file."""
|
|
return await self.request("DELETE", f"/files/{id}")
|
|
|
|
async def delete_files(self, ids: list[str]) -> dict[str, Any]:
|
|
"""Delete multiple files."""
|
|
return await self.request("DELETE", "/files", json_data=ids)
|
|
|
|
async def import_file_url(self, url: str, data: dict | None = None) -> dict[str, Any]:
|
|
"""Import file from URL."""
|
|
body = {"url": url}
|
|
if data:
|
|
body["data"] = data
|
|
return await self.request("POST", "/files/import", json_data=body)
|
|
|
|
# =====================
|
|
# FOLDERS
|
|
# =====================
|
|
|
|
async def list_folders(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
search: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""List folders."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
if search:
|
|
params["search"] = search
|
|
|
|
return await self.request("GET", "/folders", params=params)
|
|
|
|
async def get_folder(self, id: str) -> dict[str, Any]:
|
|
"""Get folder by ID."""
|
|
return await self.request("GET", f"/folders/{id}")
|
|
|
|
async def create_folder(self, name: str, parent: str | None = None) -> dict[str, Any]:
|
|
"""Create a folder."""
|
|
data = {"name": name}
|
|
if parent:
|
|
data["parent"] = parent
|
|
return await self.request("POST", "/folders", json_data=data)
|
|
|
|
async def update_folder(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update folder."""
|
|
return await self.request("PATCH", f"/folders/{id}", json_data=data)
|
|
|
|
async def delete_folder(self, id: str) -> dict[str, Any]:
|
|
"""Delete a folder."""
|
|
return await self.request("DELETE", f"/folders/{id}")
|
|
|
|
# =====================
|
|
# USERS
|
|
# =====================
|
|
|
|
async def list_users(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
search: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""List users."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
if search:
|
|
params["search"] = search
|
|
|
|
return await self.request("GET", "/users", params=params)
|
|
|
|
async def get_user(self, id: str) -> dict[str, Any]:
|
|
"""Get user by ID."""
|
|
return await self.request("GET", f"/users/{id}")
|
|
|
|
async def get_current_user(self) -> dict[str, Any]:
|
|
"""Get current authenticated user."""
|
|
return await self.request("GET", "/users/me")
|
|
|
|
async def create_user(
|
|
self,
|
|
email: str,
|
|
password: str,
|
|
role: str,
|
|
first_name: str | None = None,
|
|
last_name: str | None = None,
|
|
status: str = "active",
|
|
**kwargs,
|
|
) -> dict[str, Any]:
|
|
"""Create a new user."""
|
|
data = {"email": email, "password": password, "role": role, "status": status}
|
|
if first_name:
|
|
data["first_name"] = first_name
|
|
if last_name:
|
|
data["last_name"] = last_name
|
|
data.update(kwargs)
|
|
|
|
return await self.request("POST", "/users", json_data=data)
|
|
|
|
async def update_user(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a user."""
|
|
return await self.request("PATCH", f"/users/{id}", json_data=data)
|
|
|
|
async def delete_user(self, id: str) -> dict[str, Any]:
|
|
"""Delete a user."""
|
|
return await self.request("DELETE", f"/users/{id}")
|
|
|
|
async def delete_users(self, ids: list[str]) -> dict[str, Any]:
|
|
"""Delete multiple users."""
|
|
return await self.request("DELETE", "/users", json_data=ids)
|
|
|
|
async def invite_user(
|
|
self, email: str, role: str, invite_url: str | None = None
|
|
) -> dict[str, Any]:
|
|
"""Invite a user."""
|
|
data = {"email": email, "role": role}
|
|
if invite_url:
|
|
data["invite_url"] = invite_url
|
|
|
|
return await self.request("POST", "/users/invite", json_data=data)
|
|
|
|
# =====================
|
|
# ROLES
|
|
# =====================
|
|
|
|
async def list_roles(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List roles."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/roles", params=params)
|
|
|
|
async def get_role(self, id: str) -> dict[str, Any]:
|
|
"""Get role by ID."""
|
|
return await self.request("GET", f"/roles/{id}")
|
|
|
|
async def create_role(
|
|
self,
|
|
name: str,
|
|
icon: str = "supervised_user_circle",
|
|
description: str | None = None,
|
|
admin_access: bool = False,
|
|
app_access: bool = True,
|
|
**kwargs,
|
|
) -> dict[str, Any]:
|
|
"""Create a new role."""
|
|
data = {"name": name, "icon": icon, "admin_access": admin_access, "app_access": app_access}
|
|
if description:
|
|
data["description"] = description
|
|
data.update(kwargs)
|
|
|
|
return await self.request("POST", "/roles", json_data=data)
|
|
|
|
async def update_role(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a role."""
|
|
return await self.request("PATCH", f"/roles/{id}", json_data=data)
|
|
|
|
async def delete_role(self, id: str) -> dict[str, Any]:
|
|
"""Delete a role."""
|
|
return await self.request("DELETE", f"/roles/{id}")
|
|
|
|
# =====================
|
|
# PERMISSIONS
|
|
# =====================
|
|
|
|
async def list_permissions(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List permissions."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/permissions", params=params)
|
|
|
|
async def get_permission(self, id: str) -> dict[str, Any]:
|
|
"""Get permission by ID."""
|
|
return await self.request("GET", f"/permissions/{id}")
|
|
|
|
async def get_my_permissions(self) -> dict[str, Any]:
|
|
"""Get current user's permissions."""
|
|
return await self.request("GET", "/permissions/me")
|
|
|
|
async def create_permission(
|
|
self,
|
|
collection: str,
|
|
action: str,
|
|
policy: str,
|
|
role: str | None = None,
|
|
permissions: dict | None = None,
|
|
validation: dict | None = None,
|
|
presets: dict | None = None,
|
|
fields: list[str] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new permission. Requires 'policy' in Directus v10+."""
|
|
data = {
|
|
"collection": collection,
|
|
"action": action,
|
|
"policy": policy, # Required in Directus v10+
|
|
}
|
|
if role:
|
|
data["role"] = role
|
|
if permissions:
|
|
data["permissions"] = permissions
|
|
if validation:
|
|
data["validation"] = validation
|
|
if presets:
|
|
data["presets"] = presets
|
|
if fields:
|
|
data["fields"] = fields
|
|
|
|
return await self.request("POST", "/permissions", json_data=data)
|
|
|
|
async def update_permission(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a permission."""
|
|
return await self.request("PATCH", f"/permissions/{id}", json_data=data)
|
|
|
|
async def delete_permission(self, id: str) -> dict[str, Any]:
|
|
"""Delete a permission."""
|
|
return await self.request("DELETE", f"/permissions/{id}")
|
|
|
|
# =====================
|
|
# POLICIES
|
|
# =====================
|
|
|
|
async def list_policies(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List policies."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/policies", params=params)
|
|
|
|
async def get_policy(self, id: str) -> dict[str, Any]:
|
|
"""Get policy by ID."""
|
|
return await self.request("GET", f"/policies/{id}")
|
|
|
|
# =====================
|
|
# FLOWS
|
|
# =====================
|
|
|
|
async def list_flows(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List flows."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/flows", params=params)
|
|
|
|
async def get_flow(self, id: str) -> dict[str, Any]:
|
|
"""Get flow by ID."""
|
|
return await self.request("GET", f"/flows/{id}")
|
|
|
|
async def create_flow(
|
|
self,
|
|
name: str,
|
|
trigger: str,
|
|
status: str = "active",
|
|
icon: str = "bolt",
|
|
options: dict | None = None,
|
|
accountability: str | None = None,
|
|
description: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new flow."""
|
|
data = {"name": name, "trigger": trigger, "status": status, "icon": icon}
|
|
if options:
|
|
data["options"] = options
|
|
if accountability:
|
|
data["accountability"] = accountability
|
|
if description:
|
|
data["description"] = description
|
|
|
|
return await self.request("POST", "/flows", json_data=data)
|
|
|
|
async def update_flow(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a flow."""
|
|
return await self.request("PATCH", f"/flows/{id}", json_data=data)
|
|
|
|
async def delete_flow(self, id: str) -> dict[str, Any]:
|
|
"""Delete a flow."""
|
|
return await self.request("DELETE", f"/flows/{id}")
|
|
|
|
async def trigger_flow(self, id: str, data: dict | None = None) -> dict[str, Any]:
|
|
"""Trigger a manual flow."""
|
|
return await self.request("POST", f"/flows/trigger/{id}", json_data=data or {})
|
|
|
|
# =====================
|
|
# OPERATIONS
|
|
# =====================
|
|
|
|
async def list_operations(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List operations."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/operations", params=params)
|
|
|
|
async def get_operation(self, id: str) -> dict[str, Any]:
|
|
"""Get operation by ID."""
|
|
return await self.request("GET", f"/operations/{id}")
|
|
|
|
async def create_operation(
|
|
self,
|
|
flow: str,
|
|
type: str,
|
|
name: str | None = None,
|
|
key: str | None = None,
|
|
options: dict | None = None,
|
|
position_x: int = 0,
|
|
position_y: int = 0,
|
|
resolve: str | None = None,
|
|
reject: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new operation."""
|
|
data = {"flow": flow, "type": type, "position_x": position_x, "position_y": position_y}
|
|
if name:
|
|
data["name"] = name
|
|
if key:
|
|
data["key"] = key
|
|
if options:
|
|
data["options"] = options
|
|
if resolve:
|
|
data["resolve"] = resolve
|
|
if reject:
|
|
data["reject"] = reject
|
|
|
|
return await self.request("POST", "/operations", json_data=data)
|
|
|
|
async def update_operation(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update an operation."""
|
|
return await self.request("PATCH", f"/operations/{id}", json_data=data)
|
|
|
|
async def delete_operation(self, id: str) -> dict[str, Any]:
|
|
"""Delete an operation."""
|
|
return await self.request("DELETE", f"/operations/{id}")
|
|
|
|
# =====================
|
|
# WEBHOOKS
|
|
# =====================
|
|
|
|
async def list_webhooks(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List webhooks."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/webhooks", params=params)
|
|
|
|
async def get_webhook(self, id: str) -> dict[str, Any]:
|
|
"""Get webhook by ID."""
|
|
return await self.request("GET", f"/webhooks/{id}")
|
|
|
|
async def create_webhook(
|
|
self,
|
|
name: str,
|
|
url: str,
|
|
actions: list[str],
|
|
collections: list[str],
|
|
method: str = "POST",
|
|
status: str = "active",
|
|
headers: dict | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new webhook."""
|
|
data = {
|
|
"name": name,
|
|
"url": url,
|
|
"actions": _ensure_list(actions),
|
|
"collections": _ensure_list(collections),
|
|
"method": method,
|
|
"status": status,
|
|
}
|
|
if headers:
|
|
data["headers"] = headers
|
|
|
|
return await self.request("POST", "/webhooks", json_data=data)
|
|
|
|
async def update_webhook(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a webhook."""
|
|
return await self.request("PATCH", f"/webhooks/{id}", json_data=data)
|
|
|
|
async def delete_webhook(self, id: str) -> dict[str, Any]:
|
|
"""Delete a webhook."""
|
|
return await self.request("DELETE", f"/webhooks/{id}")
|
|
|
|
# =====================
|
|
# ACTIVITY
|
|
# =====================
|
|
|
|
async def list_activity(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List activity."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/activity", params=params)
|
|
|
|
async def get_activity(self, id: str) -> dict[str, Any]:
|
|
"""Get activity by ID."""
|
|
return await self.request("GET", f"/activity/{id}")
|
|
|
|
# =====================
|
|
# REVISIONS
|
|
# =====================
|
|
|
|
async def list_revisions(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List revisions."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/revisions", params=params)
|
|
|
|
async def get_revision(self, id: str) -> dict[str, Any]:
|
|
"""Get revision by ID."""
|
|
return await self.request("GET", f"/revisions/{id}")
|
|
|
|
# =====================
|
|
# VERSIONS
|
|
# =====================
|
|
|
|
async def list_versions(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List content versions."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/versions", params=params)
|
|
|
|
async def get_version(self, id: str) -> dict[str, Any]:
|
|
"""Get version by ID."""
|
|
return await self.request("GET", f"/versions/{id}")
|
|
|
|
async def create_version(
|
|
self, name: str, collection: str, item: str, key: str
|
|
) -> dict[str, Any]:
|
|
"""Create a new version."""
|
|
data = {"name": name, "collection": collection, "item": item, "key": key}
|
|
|
|
return await self.request("POST", "/versions", json_data=data)
|
|
|
|
async def update_version(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a version."""
|
|
return await self.request("PATCH", f"/versions/{id}", json_data=data)
|
|
|
|
async def delete_version(self, id: str) -> dict[str, Any]:
|
|
"""Delete a version."""
|
|
return await self.request("DELETE", f"/versions/{id}")
|
|
|
|
async def promote_version(self, id: str, mainHash: str | None = None) -> dict[str, Any]:
|
|
"""Promote a version to main."""
|
|
data = {}
|
|
if mainHash:
|
|
data["mainHash"] = mainHash
|
|
return await self.request("POST", f"/versions/{id}/promote", json_data=data)
|
|
|
|
# =====================
|
|
# COMMENTS
|
|
# =====================
|
|
|
|
async def list_comments(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List comments."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/comments", params=params)
|
|
|
|
async def get_comment(self, id: str) -> dict[str, Any]:
|
|
"""Get comment by ID."""
|
|
return await self.request("GET", f"/comments/{id}")
|
|
|
|
async def create_comment(self, collection: str, item: str, comment: str) -> dict[str, Any]:
|
|
"""Create a new comment."""
|
|
data = {"collection": collection, "item": item, "comment": comment}
|
|
return await self.request("POST", "/comments", json_data=data)
|
|
|
|
async def update_comment(self, id: str, comment: str) -> dict[str, Any]:
|
|
"""Update a comment."""
|
|
return await self.request("PATCH", f"/comments/{id}", json_data={"comment": comment})
|
|
|
|
async def delete_comment(self, id: str) -> dict[str, Any]:
|
|
"""Delete a comment."""
|
|
return await self.request("DELETE", f"/comments/{id}")
|
|
|
|
# =====================
|
|
# DASHBOARDS
|
|
# =====================
|
|
|
|
async def list_dashboards(
|
|
self,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
) -> dict[str, Any]:
|
|
"""List dashboards."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
if sort:
|
|
params["sort"] = ",".join(_ensure_list(sort))
|
|
|
|
return await self.request("GET", "/dashboards", params=params)
|
|
|
|
async def get_dashboard(self, id: str) -> dict[str, Any]:
|
|
"""Get dashboard by ID."""
|
|
return await self.request("GET", f"/dashboards/{id}")
|
|
|
|
async def create_dashboard(
|
|
self, name: str, icon: str = "dashboard", note: str | None = None, color: str | None = None
|
|
) -> dict[str, Any]:
|
|
"""Create a new dashboard."""
|
|
data = {"name": name, "icon": icon}
|
|
if note:
|
|
data["note"] = note
|
|
if color:
|
|
data["color"] = color
|
|
|
|
return await self.request("POST", "/dashboards", json_data=data)
|
|
|
|
async def update_dashboard(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a dashboard."""
|
|
return await self.request("PATCH", f"/dashboards/{id}", json_data=data)
|
|
|
|
async def delete_dashboard(self, id: str) -> dict[str, Any]:
|
|
"""Delete a dashboard."""
|
|
return await self.request("DELETE", f"/dashboards/{id}")
|
|
|
|
# =====================
|
|
# PANELS
|
|
# =====================
|
|
|
|
async def list_panels(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List panels."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/panels", params=params)
|
|
|
|
async def get_panel(self, id: str) -> dict[str, Any]:
|
|
"""Get panel by ID."""
|
|
return await self.request("GET", f"/panels/{id}")
|
|
|
|
async def create_panel(
|
|
self,
|
|
dashboard: str,
|
|
type: str,
|
|
name: str | None = None,
|
|
icon: str | None = None,
|
|
color: str | None = None,
|
|
note: str | None = None,
|
|
width: int = 12,
|
|
height: int = 6,
|
|
position_x: int = 0,
|
|
position_y: int = 0,
|
|
options: dict | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new panel."""
|
|
data = {
|
|
"dashboard": dashboard,
|
|
"type": type,
|
|
"width": width,
|
|
"height": height,
|
|
"position_x": position_x,
|
|
"position_y": position_y,
|
|
}
|
|
if name:
|
|
data["name"] = name
|
|
if icon:
|
|
data["icon"] = icon
|
|
if color:
|
|
data["color"] = color
|
|
if note:
|
|
data["note"] = note
|
|
if options:
|
|
data["options"] = options
|
|
|
|
return await self.request("POST", "/panels", json_data=data)
|
|
|
|
async def update_panel(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a panel."""
|
|
return await self.request("PATCH", f"/panels/{id}", json_data=data)
|
|
|
|
async def delete_panel(self, id: str) -> dict[str, Any]:
|
|
"""Delete a panel."""
|
|
return await self.request("DELETE", f"/panels/{id}")
|
|
|
|
# =====================
|
|
# SETTINGS
|
|
# =====================
|
|
|
|
async def get_settings(self) -> dict[str, Any]:
|
|
"""Get system settings."""
|
|
return await self.request("GET", "/settings")
|
|
|
|
async def update_settings(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update system settings."""
|
|
return await self.request("PATCH", "/settings", json_data=data)
|
|
|
|
# =====================
|
|
# SERVER
|
|
# =====================
|
|
|
|
async def get_server_info(self) -> dict[str, Any]:
|
|
"""Get server info."""
|
|
return await self.request("GET", "/server/info")
|
|
|
|
async def health_check(self) -> dict[str, Any]:
|
|
"""Check server health."""
|
|
return await self.request("GET", "/server/health")
|
|
|
|
async def get_graphql_sdl(self) -> dict[str, Any]:
|
|
"""Get GraphQL SDL schema."""
|
|
return await self.request("GET", "/server/specs/graphql")
|
|
|
|
async def get_openapi_spec(self) -> dict[str, Any]:
|
|
"""Get OpenAPI specification."""
|
|
return await self.request("GET", "/server/specs/oas")
|
|
|
|
# =====================
|
|
# SCHEMA
|
|
# =====================
|
|
|
|
async def get_schema_snapshot(self) -> dict[str, Any]:
|
|
"""Get schema snapshot."""
|
|
return await self.request("GET", "/schema/snapshot")
|
|
|
|
async def schema_diff(self, snapshot: dict[str, Any]) -> dict[str, Any]:
|
|
"""Get diff between current schema and snapshot."""
|
|
return await self.request("POST", "/schema/diff", json_data=snapshot)
|
|
|
|
async def schema_apply(self, diff: dict[str, Any]) -> dict[str, Any]:
|
|
"""Apply schema diff."""
|
|
return await self.request("POST", "/schema/apply", json_data=diff)
|
|
|
|
# =====================
|
|
# PRESETS
|
|
# =====================
|
|
|
|
async def list_presets(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List presets."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/presets", params=params)
|
|
|
|
async def get_preset(self, id: str) -> dict[str, Any]:
|
|
"""Get preset by ID."""
|
|
return await self.request("GET", f"/presets/{id}")
|
|
|
|
async def create_preset(
|
|
self,
|
|
collection: str,
|
|
layout: str | None = None,
|
|
layout_query: dict | None = None,
|
|
layout_options: dict | None = None,
|
|
filter: dict | None = None,
|
|
search: str | None = None,
|
|
bookmark: str | None = None,
|
|
user: str | None = None,
|
|
role: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new preset."""
|
|
data = {"collection": collection}
|
|
if layout:
|
|
data["layout"] = layout
|
|
if layout_query:
|
|
data["layout_query"] = layout_query
|
|
if layout_options:
|
|
data["layout_options"] = layout_options
|
|
if filter:
|
|
data["filter"] = filter
|
|
if search:
|
|
data["search"] = search
|
|
if bookmark:
|
|
data["bookmark"] = bookmark
|
|
if user:
|
|
data["user"] = user
|
|
if role:
|
|
data["role"] = role
|
|
|
|
return await self.request("POST", "/presets", json_data=data)
|
|
|
|
async def update_preset(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a preset."""
|
|
return await self.request("PATCH", f"/presets/{id}", json_data=data)
|
|
|
|
async def delete_preset(self, id: str) -> dict[str, Any]:
|
|
"""Delete a preset."""
|
|
return await self.request("DELETE", f"/presets/{id}")
|
|
|
|
# =====================
|
|
# SHARES
|
|
# =====================
|
|
|
|
async def list_shares(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List shares."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/shares", params=params)
|
|
|
|
async def get_share(self, id: str) -> dict[str, Any]:
|
|
"""Get share by ID."""
|
|
return await self.request("GET", f"/shares/{id}")
|
|
|
|
async def create_share(
|
|
self,
|
|
collection: str,
|
|
item: str,
|
|
name: str | None = None,
|
|
role: str | None = None,
|
|
password: str | None = None,
|
|
date_start: str | None = None,
|
|
date_end: str | None = None,
|
|
max_uses: int | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new share."""
|
|
data = {"collection": collection, "item": item}
|
|
if name:
|
|
data["name"] = name
|
|
if role:
|
|
data["role"] = role
|
|
if password:
|
|
data["password"] = password
|
|
if date_start:
|
|
data["date_start"] = date_start
|
|
if date_end:
|
|
data["date_end"] = date_end
|
|
if max_uses:
|
|
data["max_uses"] = max_uses
|
|
|
|
return await self.request("POST", "/shares", json_data=data)
|
|
|
|
async def update_share(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a share."""
|
|
return await self.request("PATCH", f"/shares/{id}", json_data=data)
|
|
|
|
async def delete_share(self, id: str) -> dict[str, Any]:
|
|
"""Delete a share."""
|
|
return await self.request("DELETE", f"/shares/{id}")
|
|
|
|
# =====================
|
|
# NOTIFICATIONS
|
|
# =====================
|
|
|
|
async def list_notifications(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List notifications."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/notifications", params=params)
|
|
|
|
async def get_notification(self, id: str) -> dict[str, Any]:
|
|
"""Get notification by ID."""
|
|
return await self.request("GET", f"/notifications/{id}")
|
|
|
|
async def update_notification(self, id: str, status: str) -> dict[str, Any]:
|
|
"""Update notification (mark as read/archived)."""
|
|
return await self.request("PATCH", f"/notifications/{id}", json_data={"status": status})
|
|
|
|
async def delete_notification(self, id: str) -> dict[str, Any]:
|
|
"""Delete a notification."""
|
|
return await self.request("DELETE", f"/notifications/{id}")
|
|
|
|
# =====================
|
|
# TRANSLATIONS
|
|
# =====================
|
|
|
|
async def list_translations(
|
|
self, filter: dict | None = None, limit: int = 100, offset: int = 0
|
|
) -> dict[str, Any]:
|
|
"""List translations."""
|
|
params = {"limit": limit, "offset": offset}
|
|
if filter:
|
|
params["filter"] = json.dumps(filter)
|
|
|
|
return await self.request("GET", "/translations", params=params)
|
|
|
|
async def get_translation(self, id: str) -> dict[str, Any]:
|
|
"""Get translation by ID."""
|
|
return await self.request("GET", f"/translations/{id}")
|
|
|
|
async def create_translation(self, key: str, language: str, value: str) -> dict[str, Any]:
|
|
"""Create a new translation."""
|
|
data = {"key": key, "language": language, "value": value}
|
|
return await self.request("POST", "/translations", json_data=data)
|
|
|
|
async def update_translation(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update a translation."""
|
|
return await self.request("PATCH", f"/translations/{id}", json_data=data)
|
|
|
|
async def delete_translation(self, id: str) -> dict[str, Any]:
|
|
"""Delete a translation."""
|
|
return await self.request("DELETE", f"/translations/{id}")
|