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:
9
plugins/gitea/__init__.py
Normal file
9
plugins/gitea/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Gitea Plugin
|
||||
|
||||
Complete Gitea management through REST API.
|
||||
"""
|
||||
|
||||
from .plugin import GiteaPlugin
|
||||
|
||||
__all__ = ["GiteaPlugin"]
|
||||
451
plugins/gitea/client.py
Normal file
451
plugins/gitea/client.py
Normal file
@@ -0,0 +1,451 @@
|
||||
"""
|
||||
Gitea REST API Client
|
||||
|
||||
Handles all HTTP communication with Gitea REST API.
|
||||
Separates API communication from business logic.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
||||
class GiteaClient:
|
||||
"""
|
||||
Gitea REST API client for HTTP communication.
|
||||
|
||||
Handles authentication, request formatting, and error handling
|
||||
for all Gitea API endpoints.
|
||||
"""
|
||||
|
||||
def __init__(self, site_url: str, token: str | None = None, oauth_enabled: bool = False):
|
||||
"""
|
||||
Initialize Gitea API client.
|
||||
|
||||
Args:
|
||||
site_url: Gitea instance URL (e.g., https://gitea.example.com)
|
||||
token: Personal access token for authentication
|
||||
oauth_enabled: Whether OAuth is enabled for this site
|
||||
"""
|
||||
self.site_url = site_url.rstrip("/")
|
||||
self.api_base = f"{self.site_url}/api/v1"
|
||||
self.token = token
|
||||
self.oauth_enabled = oauth_enabled
|
||||
|
||||
# Initialize logger
|
||||
self.logger = logging.getLogger(f"GiteaClient.{site_url}")
|
||||
|
||||
def _get_headers(self, additional_headers: dict | None = None) -> dict[str, str]:
|
||||
"""
|
||||
Get request headers with authentication.
|
||||
|
||||
Args:
|
||||
additional_headers: Additional headers to include
|
||||
|
||||
Returns:
|
||||
Dict: Headers with authentication
|
||||
"""
|
||||
headers = {"Content-Type": "application/json", "accept": "application/json"}
|
||||
|
||||
# Add token authentication if available
|
||||
if self.token:
|
||||
headers["Authorization"] = f"token {self.token}"
|
||||
|
||||
# Merge additional headers
|
||||
if additional_headers:
|
||||
headers.update(additional_headers)
|
||||
|
||||
return headers
|
||||
|
||||
async def request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
params: dict | None = None,
|
||||
json_data: dict | None = None,
|
||||
headers_override: dict | None = None,
|
||||
) -> Any:
|
||||
"""
|
||||
Make authenticated request to Gitea REST API.
|
||||
|
||||
Args:
|
||||
method: HTTP method (GET, POST, PUT, DELETE, PATCH)
|
||||
endpoint: API endpoint (without base URL)
|
||||
params: Query parameters
|
||||
json_data: JSON body data
|
||||
headers_override: Override default headers
|
||||
|
||||
Returns:
|
||||
API response (dict, list, or None)
|
||||
|
||||
Raises:
|
||||
Exception: On API errors with status code and message
|
||||
"""
|
||||
# Build full URL
|
||||
url = f"{self.api_base}/{endpoint.lstrip('/')}"
|
||||
|
||||
# Setup headers
|
||||
headers = self._get_headers(headers_override)
|
||||
|
||||
# Filter out None values from params
|
||||
if params:
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
# Filter None values from JSON data
|
||||
if json_data:
|
||||
json_data = {k: v for k, v in json_data.items() if v is not None}
|
||||
|
||||
# Make request
|
||||
self.logger.debug(f"{method} {url}")
|
||||
self.logger.debug(f"Params: {params}")
|
||||
self.logger.debug(f"Data: {json_data}")
|
||||
|
||||
async with (
|
||||
aiohttp.ClientSession() as session,
|
||||
session.request(
|
||||
method=method, url=url, params=params, json=json_data, headers=headers
|
||||
) as response,
|
||||
):
|
||||
# Log response
|
||||
self.logger.debug(f"Response status: {response.status}")
|
||||
|
||||
# Handle empty responses (e.g., 204 No Content)
|
||||
if response.status == 204:
|
||||
return {"success": True, "message": "Operation completed successfully"}
|
||||
|
||||
# Try to parse JSON response
|
||||
try:
|
||||
response_data = await response.json()
|
||||
except Exception:
|
||||
response_text = await response.text()
|
||||
if response.status >= 400:
|
||||
raise Exception(f"Gitea API error (status {response.status}): {response_text}")
|
||||
return {"success": True, "message": response_text}
|
||||
|
||||
# Check for errors
|
||||
if response.status >= 400:
|
||||
error_msg = response_data.get("message", "Unknown error")
|
||||
raise Exception(f"Gitea API error (status {response.status}): {error_msg}")
|
||||
|
||||
return response_data
|
||||
|
||||
# Repository endpoints
|
||||
async def list_repositories(
|
||||
self, owner: str | None = None, page: int = 1, limit: int = 30
|
||||
) -> list[dict]:
|
||||
"""List repositories for a user/org or current user"""
|
||||
if owner:
|
||||
endpoint = f"users/{owner}/repos"
|
||||
else:
|
||||
endpoint = "user/repos"
|
||||
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", endpoint, params=params)
|
||||
|
||||
async def get_repository(self, owner: str, repo: str) -> dict:
|
||||
"""Get repository details"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}")
|
||||
|
||||
async def create_repository(self, data: dict, org: str | None = None) -> dict:
|
||||
"""Create a new repository"""
|
||||
if org:
|
||||
endpoint = f"orgs/{org}/repos"
|
||||
else:
|
||||
endpoint = "user/repos"
|
||||
return await self.request("POST", endpoint, json_data=data)
|
||||
|
||||
async def update_repository(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Update repository settings"""
|
||||
return await self.request("PATCH", f"repos/{owner}/{repo}", json_data=data)
|
||||
|
||||
async def delete_repository(self, owner: str, repo: str) -> dict:
|
||||
"""Delete a repository"""
|
||||
return await self.request("DELETE", f"repos/{owner}/{repo}")
|
||||
|
||||
# Branch endpoints
|
||||
async def list_branches(
|
||||
self, owner: str, repo: str, page: int = 1, limit: int = 30
|
||||
) -> list[dict]:
|
||||
"""List repository branches"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/branches", params=params)
|
||||
|
||||
async def get_branch(self, owner: str, repo: str, branch: str) -> dict:
|
||||
"""Get branch details"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/branches/{branch}")
|
||||
|
||||
async def create_branch(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a new branch"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/branches", json_data=data)
|
||||
|
||||
async def delete_branch(self, owner: str, repo: str, branch: str) -> dict:
|
||||
"""Delete a branch"""
|
||||
return await self.request("DELETE", f"repos/{owner}/{repo}/branches/{branch}")
|
||||
|
||||
# Tag endpoints
|
||||
async def list_tags(self, owner: str, repo: str, page: int = 1, limit: int = 30) -> list[dict]:
|
||||
"""List repository tags"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/tags", params=params)
|
||||
|
||||
async def create_tag(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a new tag"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/tags", json_data=data)
|
||||
|
||||
async def delete_tag(self, owner: str, repo: str, tag: str) -> dict:
|
||||
"""Delete a tag"""
|
||||
return await self.request("DELETE", f"repos/{owner}/{repo}/tags/{tag}")
|
||||
|
||||
# File endpoints
|
||||
async def get_file(self, owner: str, repo: str, filepath: str, ref: str | None = None) -> dict:
|
||||
"""Get file contents"""
|
||||
params = {"ref": ref} if ref else {}
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/contents/{filepath}", params=params)
|
||||
|
||||
async def create_file(self, owner: str, repo: str, filepath: str, data: dict) -> dict:
|
||||
"""Create a file"""
|
||||
# Encode content to base64 if not already encoded
|
||||
if "content" in data:
|
||||
content = data["content"]
|
||||
|
||||
# Check if content is already base64 encoded
|
||||
is_already_base64 = data.get("content_is_base64", False)
|
||||
|
||||
if not is_already_base64:
|
||||
# Plain text content - encode to base64
|
||||
try:
|
||||
data["content"] = base64.b64encode(content.encode()).decode()
|
||||
except (AttributeError, UnicodeDecodeError):
|
||||
# If content is already bytes or has encoding issues, try direct encoding
|
||||
if isinstance(content, bytes):
|
||||
data["content"] = base64.b64encode(content).decode()
|
||||
else:
|
||||
raise ValueError("Content must be a string or bytes")
|
||||
|
||||
# Remove the flag before sending to API
|
||||
data.pop("content_is_base64", None)
|
||||
|
||||
return await self.request(
|
||||
"POST", f"repos/{owner}/{repo}/contents/{filepath}", json_data=data
|
||||
)
|
||||
|
||||
async def update_file(self, owner: str, repo: str, filepath: str, data: dict) -> dict:
|
||||
"""Update a file"""
|
||||
# Encode content to base64 if not already encoded
|
||||
if "content" in data:
|
||||
content = data["content"]
|
||||
|
||||
# Check if content is already base64 encoded
|
||||
is_already_base64 = data.get("content_is_base64", False)
|
||||
|
||||
if not is_already_base64:
|
||||
# Plain text content - encode to base64
|
||||
try:
|
||||
data["content"] = base64.b64encode(content.encode()).decode()
|
||||
except (AttributeError, UnicodeDecodeError):
|
||||
# If content is already bytes or has encoding issues, try direct encoding
|
||||
if isinstance(content, bytes):
|
||||
data["content"] = base64.b64encode(content).decode()
|
||||
else:
|
||||
raise ValueError("Content must be a string or bytes")
|
||||
|
||||
# Remove the flag before sending to API
|
||||
data.pop("content_is_base64", None)
|
||||
|
||||
return await self.request(
|
||||
"PUT", f"repos/{owner}/{repo}/contents/{filepath}", json_data=data
|
||||
)
|
||||
|
||||
async def delete_file(
|
||||
self,
|
||||
owner: str,
|
||||
repo: str,
|
||||
filepath: str,
|
||||
sha: str,
|
||||
message: str,
|
||||
branch: str | None = None,
|
||||
) -> dict:
|
||||
"""Delete a file"""
|
||||
data = {"sha": sha, "message": message}
|
||||
if branch:
|
||||
data["branch"] = branch
|
||||
return await self.request(
|
||||
"DELETE", f"repos/{owner}/{repo}/contents/{filepath}", json_data=data
|
||||
)
|
||||
|
||||
# Issue endpoints
|
||||
async def list_issues(self, owner: str, repo: str, params: dict) -> list[dict]:
|
||||
"""List repository issues"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/issues", params=params)
|
||||
|
||||
async def get_issue(self, owner: str, repo: str, index: int) -> dict:
|
||||
"""Get issue details"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/issues/{index}")
|
||||
|
||||
async def create_issue(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a new issue"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/issues", json_data=data)
|
||||
|
||||
async def update_issue(self, owner: str, repo: str, index: int, data: dict) -> dict:
|
||||
"""Update an issue"""
|
||||
return await self.request("PATCH", f"repos/{owner}/{repo}/issues/{index}", json_data=data)
|
||||
|
||||
async def list_issue_comments(self, owner: str, repo: str, index: int) -> list[dict]:
|
||||
"""List issue comments"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/issues/{index}/comments")
|
||||
|
||||
async def create_issue_comment(self, owner: str, repo: str, index: int, data: dict) -> dict:
|
||||
"""Create issue comment"""
|
||||
return await self.request(
|
||||
"POST", f"repos/{owner}/{repo}/issues/{index}/comments", json_data=data
|
||||
)
|
||||
|
||||
# Label endpoints
|
||||
async def list_labels(self, owner: str, repo: str) -> list[dict]:
|
||||
"""List repository labels"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/labels")
|
||||
|
||||
async def create_label(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a label"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/labels", json_data=data)
|
||||
|
||||
async def delete_label(self, owner: str, repo: str, label_id: int) -> dict:
|
||||
"""Delete a label"""
|
||||
return await self.request("DELETE", f"repos/{owner}/{repo}/labels/{label_id}")
|
||||
|
||||
# Milestone endpoints
|
||||
async def list_milestones(self, owner: str, repo: str, state: str | None = None) -> list[dict]:
|
||||
"""List repository milestones"""
|
||||
params = {"state": state} if state else {}
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/milestones", params=params)
|
||||
|
||||
async def create_milestone(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a milestone"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/milestones", json_data=data)
|
||||
|
||||
# Pull Request endpoints
|
||||
async def list_pull_requests(self, owner: str, repo: str, params: dict) -> list[dict]:
|
||||
"""List repository pull requests"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/pulls", params=params)
|
||||
|
||||
async def get_pull_request(self, owner: str, repo: str, index: int) -> dict:
|
||||
"""Get pull request details"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}")
|
||||
|
||||
async def create_pull_request(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a new pull request"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/pulls", json_data=data)
|
||||
|
||||
async def update_pull_request(self, owner: str, repo: str, index: int, data: dict) -> dict:
|
||||
"""Update a pull request"""
|
||||
return await self.request("PATCH", f"repos/{owner}/{repo}/pulls/{index}", json_data=data)
|
||||
|
||||
async def merge_pull_request(self, owner: str, repo: str, index: int, data: dict) -> dict:
|
||||
"""Merge a pull request"""
|
||||
return await self.request(
|
||||
"POST", f"repos/{owner}/{repo}/pulls/{index}/merge", json_data=data
|
||||
)
|
||||
|
||||
async def list_pr_commits(self, owner: str, repo: str, index: int) -> list[dict]:
|
||||
"""List pull request commits"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}/commits")
|
||||
|
||||
async def list_pr_files(self, owner: str, repo: str, index: int) -> list[dict]:
|
||||
"""List pull request files"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}/files")
|
||||
|
||||
async def get_pr_diff(self, owner: str, repo: str, index: int) -> str:
|
||||
"""Get pull request diff"""
|
||||
# Override accept header for diff
|
||||
headers = {"accept": "text/plain"}
|
||||
response = await self.request(
|
||||
"GET", f"repos/{owner}/{repo}/pulls/{index}.diff", headers_override=headers
|
||||
)
|
||||
return response
|
||||
|
||||
async def list_pr_reviews(self, owner: str, repo: str, index: int) -> list[dict]:
|
||||
"""List pull request reviews"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/pulls/{index}/reviews")
|
||||
|
||||
async def create_pr_review(self, owner: str, repo: str, index: int, data: dict) -> dict:
|
||||
"""Create pull request review"""
|
||||
return await self.request(
|
||||
"POST", f"repos/{owner}/{repo}/pulls/{index}/reviews", json_data=data
|
||||
)
|
||||
|
||||
async def request_pr_reviewers(self, owner: str, repo: str, index: int, data: dict) -> dict:
|
||||
"""Request pull request reviewers"""
|
||||
return await self.request(
|
||||
"POST", f"repos/{owner}/{repo}/pulls/{index}/requested_reviewers", json_data=data
|
||||
)
|
||||
|
||||
# User endpoints
|
||||
async def get_user(self, username: str) -> dict:
|
||||
"""Get user information"""
|
||||
return await self.request("GET", f"users/{username}")
|
||||
|
||||
async def list_user_repos(self, username: str, page: int = 1, limit: int = 30) -> list[dict]:
|
||||
"""List user repositories"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", f"users/{username}/repos", params=params)
|
||||
|
||||
async def search_users(self, query: str | None = None, uid: int | None = None) -> list[dict]:
|
||||
"""Search users"""
|
||||
params = {}
|
||||
if query:
|
||||
params["q"] = query
|
||||
if uid:
|
||||
params["uid"] = uid
|
||||
response = await self.request("GET", "users/search", params=params)
|
||||
return response.get("data", [])
|
||||
|
||||
# Organization endpoints
|
||||
async def list_organizations(self, page: int = 1, limit: int = 30) -> list[dict]:
|
||||
"""List current user's organizations"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", "user/orgs", params=params)
|
||||
|
||||
async def get_organization(self, org: str) -> dict:
|
||||
"""Get organization information"""
|
||||
return await self.request("GET", f"orgs/{org}")
|
||||
|
||||
async def list_org_repos(self, org: str, page: int = 1, limit: int = 30) -> list[dict]:
|
||||
"""List organization repositories"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", f"orgs/{org}/repos", params=params)
|
||||
|
||||
async def list_org_teams(self, org: str, page: int = 1, limit: int = 30) -> list[dict]:
|
||||
"""List organization teams"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", f"orgs/{org}/teams", params=params)
|
||||
|
||||
async def list_team_members(self, team_id: int, page: int = 1, limit: int = 30) -> list[dict]:
|
||||
"""List team members"""
|
||||
params = {"page": page, "limit": limit}
|
||||
return await self.request("GET", f"teams/{team_id}/members", params=params)
|
||||
|
||||
# Webhook endpoints
|
||||
async def list_webhooks(self, owner: str, repo: str) -> list[dict]:
|
||||
"""List repository webhooks"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/hooks")
|
||||
|
||||
async def create_webhook(self, owner: str, repo: str, data: dict) -> dict:
|
||||
"""Create a webhook"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/hooks", json_data=data)
|
||||
|
||||
async def get_webhook(self, owner: str, repo: str, hook_id: int) -> dict:
|
||||
"""Get webhook details"""
|
||||
return await self.request("GET", f"repos/{owner}/{repo}/hooks/{hook_id}")
|
||||
|
||||
async def update_webhook(self, owner: str, repo: str, hook_id: int, data: dict) -> dict:
|
||||
"""Update a webhook"""
|
||||
return await self.request("PATCH", f"repos/{owner}/{repo}/hooks/{hook_id}", json_data=data)
|
||||
|
||||
async def delete_webhook(self, owner: str, repo: str, hook_id: int) -> dict:
|
||||
"""Delete a webhook"""
|
||||
return await self.request("DELETE", f"repos/{owner}/{repo}/hooks/{hook_id}")
|
||||
|
||||
async def test_webhook(self, owner: str, repo: str, hook_id: int) -> dict:
|
||||
"""Test a webhook"""
|
||||
return await self.request("POST", f"repos/{owner}/{repo}/hooks/{hook_id}/tests")
|
||||
15
plugins/gitea/handlers/__init__.py
Normal file
15
plugins/gitea/handlers/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Gitea Plugin Handlers
|
||||
|
||||
All tool handlers for Gitea operations.
|
||||
"""
|
||||
|
||||
from . import issues, pull_requests, repositories, users, webhooks
|
||||
|
||||
__all__ = [
|
||||
"repositories",
|
||||
"issues",
|
||||
"pull_requests",
|
||||
"users",
|
||||
"webhooks",
|
||||
]
|
||||
516
plugins/gitea/handlers/issues.py
Normal file
516
plugins/gitea/handlers/issues.py
Normal file
@@ -0,0 +1,516 @@
|
||||
"""Issue Handler - manages Gitea issues, labels, milestones, and comments"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.gitea.client import GiteaClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === ISSUES ===
|
||||
{
|
||||
"name": "list_issues",
|
||||
"method_name": "list_issues",
|
||||
"description": "List issues in a Gitea repository with filters. Returns paginated list of issues.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Filter by state",
|
||||
"enum": ["open", "closed", "all"],
|
||||
"default": "open",
|
||||
},
|
||||
"labels": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Comma-separated label IDs",
|
||||
},
|
||||
"q": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search query",
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_issue",
|
||||
"method_name": "get_issue",
|
||||
"description": "Get details of a specific issue by number.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"issue_number": {
|
||||
"type": "integer",
|
||||
"description": "Issue number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "issue_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_issue",
|
||||
"method_name": "create_issue",
|
||||
"description": "Create a new issue in a Gitea repository with optional labels, assignees, and milestone.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Issue title",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
},
|
||||
"body": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Issue description (supports Markdown)",
|
||||
},
|
||||
"assignee": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Assignee username",
|
||||
},
|
||||
"assignees": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "List of assignee usernames",
|
||||
},
|
||||
"labels": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "integer"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "List of label IDs",
|
||||
},
|
||||
"milestone": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Milestone ID",
|
||||
},
|
||||
"closed": {
|
||||
"type": "boolean",
|
||||
"description": "Create as closed",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "title"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_issue",
|
||||
"method_name": "update_issue",
|
||||
"description": "Update an existing issue. Can modify title, body, state, assignees, labels, and milestone.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"issue_number": {
|
||||
"type": "integer",
|
||||
"description": "Issue number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"title": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Issue title",
|
||||
},
|
||||
"body": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Issue description",
|
||||
},
|
||||
"state": {
|
||||
"anyOf": [{"type": "string", "enum": ["open", "closed"]}, {"type": "null"}],
|
||||
"description": "Issue state",
|
||||
},
|
||||
"assignee": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Assignee username",
|
||||
},
|
||||
"assignees": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "List of assignee usernames",
|
||||
},
|
||||
"labels": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "integer"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "List of label IDs",
|
||||
},
|
||||
"milestone": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Milestone ID",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "issue_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "close_issue",
|
||||
"method_name": "close_issue",
|
||||
"description": "Close an issue.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"issue_number": {
|
||||
"type": "integer",
|
||||
"description": "Issue number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "issue_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "reopen_issue",
|
||||
"method_name": "reopen_issue",
|
||||
"description": "Reopen a closed issue.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"issue_number": {
|
||||
"type": "integer",
|
||||
"description": "Issue number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "issue_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === COMMENTS ===
|
||||
{
|
||||
"name": "list_issue_comments",
|
||||
"method_name": "list_issue_comments",
|
||||
"description": "List all comments on an issue.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"issue_number": {
|
||||
"type": "integer",
|
||||
"description": "Issue number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "issue_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_issue_comment",
|
||||
"method_name": "create_issue_comment",
|
||||
"description": "Add a comment to an issue.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"issue_number": {
|
||||
"type": "integer",
|
||||
"description": "Issue number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Comment body (supports Markdown)",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "issue_number", "body"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === LABELS ===
|
||||
{
|
||||
"name": "list_labels",
|
||||
"method_name": "list_labels",
|
||||
"description": "List all labels in a repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_label",
|
||||
"method_name": "create_label",
|
||||
"description": "Create a new label in a repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Label name",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "Label color (hex without #, e.g., 'ff0000')",
|
||||
"pattern": "^[0-9A-Fa-f]{6}$",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Label description",
|
||||
"maxLength": 200,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "name", "color"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === MILESTONES ===
|
||||
{
|
||||
"name": "list_milestones",
|
||||
"method_name": "list_milestones",
|
||||
"description": "List all milestones in a repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"state": {
|
||||
"anyOf": [
|
||||
{"type": "string", "enum": ["open", "closed", "all"]},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Filter by state",
|
||||
"default": "open",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_milestone",
|
||||
"method_name": "create_milestone",
|
||||
"description": "Create a new milestone in a repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Milestone title",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Milestone description",
|
||||
},
|
||||
"due_on": {
|
||||
"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}],
|
||||
"description": "Due date (ISO 8601 format)",
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "State",
|
||||
"enum": ["open", "closed"],
|
||||
"default": "open",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "title"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_issues(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
state: str = "open",
|
||||
labels: str | None = None,
|
||||
q: str | None = None,
|
||||
page: int = 1,
|
||||
limit: int = 30,
|
||||
) -> str:
|
||||
"""List repository issues"""
|
||||
params = {"state": state, "labels": labels, "q": q, "page": page, "limit": limit}
|
||||
issues = await client.list_issues(owner, repo, params)
|
||||
result = {"success": True, "count": len(issues), "issues": issues}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_issue(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""Get issue details"""
|
||||
issue = await client.get_issue(owner, repo, issue_number)
|
||||
result = {"success": True, "issue": issue}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_issue(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
title: str,
|
||||
body: str | None = None,
|
||||
assignee: str | None = None,
|
||||
assignees: list[str] | None = None,
|
||||
labels: list[int] | None = None,
|
||||
milestone: int | None = None,
|
||||
closed: bool = False,
|
||||
) -> str:
|
||||
"""Create a new issue"""
|
||||
data = {
|
||||
"title": title,
|
||||
"body": body,
|
||||
"assignee": assignee,
|
||||
"assignees": assignees,
|
||||
"labels": labels,
|
||||
"milestone": milestone,
|
||||
"closed": closed,
|
||||
}
|
||||
issue = await client.create_issue(owner, repo, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Issue #{issue['number']} created successfully",
|
||||
"issue": issue,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def update_issue(
|
||||
client: GiteaClient, owner: str, repo: str, issue_number: int, **kwargs
|
||||
) -> str:
|
||||
"""Update an issue"""
|
||||
# Build update data from kwargs
|
||||
data = {
|
||||
k: v
|
||||
for k, v in kwargs.items()
|
||||
if v is not None and k not in ["owner", "repo", "issue_number"]
|
||||
}
|
||||
|
||||
issue = await client.update_issue(owner, repo, issue_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Issue #{issue_number} updated successfully",
|
||||
"issue": issue,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def close_issue(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""Close an issue"""
|
||||
data = {"state": "closed"}
|
||||
issue = await client.update_issue(owner, repo, issue_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Issue #{issue_number} closed successfully",
|
||||
"issue": issue,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def reopen_issue(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""Reopen an issue"""
|
||||
data = {"state": "open"}
|
||||
issue = await client.update_issue(owner, repo, issue_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Issue #{issue_number} reopened successfully",
|
||||
"issue": issue,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Comment operations
|
||||
async def list_issue_comments(client: GiteaClient, owner: str, repo: str, issue_number: int) -> str:
|
||||
"""List issue comments"""
|
||||
comments = await client.list_issue_comments(owner, repo, issue_number)
|
||||
result = {"success": True, "count": len(comments), "comments": comments}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_issue_comment(
|
||||
client: GiteaClient, owner: str, repo: str, issue_number: int, body: str
|
||||
) -> str:
|
||||
"""Create issue comment"""
|
||||
data = {"body": body}
|
||||
comment = await client.create_issue_comment(owner, repo, issue_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Comment added to issue #{issue_number}",
|
||||
"comment": comment,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Label operations
|
||||
async def list_labels(client: GiteaClient, owner: str, repo: str) -> str:
|
||||
"""List repository labels"""
|
||||
labels = await client.list_labels(owner, repo)
|
||||
result = {"success": True, "count": len(labels), "labels": labels}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_label(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
name: str,
|
||||
color: str,
|
||||
description: str | None = None,
|
||||
) -> str:
|
||||
"""Create a label"""
|
||||
data = {"name": name, "color": color, "description": description}
|
||||
label = await client.create_label(owner, repo, data)
|
||||
result = {"success": True, "message": f"Label '{name}' created successfully", "label": label}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Milestone operations
|
||||
async def list_milestones(
|
||||
client: GiteaClient, owner: str, repo: str, state: str | None = None
|
||||
) -> str:
|
||||
"""List repository milestones"""
|
||||
milestones = await client.list_milestones(owner, repo, state=state)
|
||||
result = {"success": True, "count": len(milestones), "milestones": milestones}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_milestone(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
title: str,
|
||||
description: str | None = None,
|
||||
due_on: str | None = None,
|
||||
state: str = "open",
|
||||
) -> str:
|
||||
"""Create a milestone"""
|
||||
data = {"title": title, "description": description, "due_on": due_on, "state": state}
|
||||
milestone = await client.create_milestone(owner, repo, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Milestone '{title}' created successfully",
|
||||
"milestone": milestone,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
629
plugins/gitea/handlers/pull_requests.py
Normal file
629
plugins/gitea/handlers/pull_requests.py
Normal file
@@ -0,0 +1,629 @@
|
||||
"""Pull Request Handler - manages Gitea pull requests, reviews, and merges"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.gitea.client import GiteaClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === PULL REQUESTS ===
|
||||
{
|
||||
"name": "list_pull_requests",
|
||||
"method_name": "list_pull_requests",
|
||||
"description": "List pull requests in a Gitea repository with filters.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Filter by state",
|
||||
"enum": ["open", "closed", "all"],
|
||||
"default": "open",
|
||||
},
|
||||
"sort": {
|
||||
"type": "string",
|
||||
"description": "Sort by",
|
||||
"enum": ["created", "updated", "comments", "recentupdate"],
|
||||
"default": "created",
|
||||
},
|
||||
"labels": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Comma-separated label IDs",
|
||||
},
|
||||
"milestone": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Milestone name",
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_pull_request",
|
||||
"method_name": "get_pull_request",
|
||||
"description": "Get details of a specific pull request by number.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_pull_request",
|
||||
"method_name": "create_pull_request",
|
||||
"description": "Create a new pull request in a Gitea repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Pull request title",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
},
|
||||
"head": {
|
||||
"type": "string",
|
||||
"description": "Source branch (head branch)",
|
||||
"minLength": 1,
|
||||
},
|
||||
"base": {
|
||||
"type": "string",
|
||||
"description": "Target branch (base branch)",
|
||||
"minLength": 1,
|
||||
},
|
||||
"body": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Pull request description (supports Markdown)",
|
||||
},
|
||||
"assignee": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Assignee username",
|
||||
},
|
||||
"assignees": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "List of assignee usernames",
|
||||
},
|
||||
"labels": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "integer"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "List of label IDs",
|
||||
},
|
||||
"milestone": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Milestone ID",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "title", "head", "base"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_pull_request",
|
||||
"method_name": "update_pull_request",
|
||||
"description": "Update an existing pull request. Can modify title, body, state, assignees, labels, and milestone.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"title": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Pull request title",
|
||||
},
|
||||
"body": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Pull request description",
|
||||
},
|
||||
"state": {
|
||||
"anyOf": [{"type": "string", "enum": ["open", "closed"]}, {"type": "null"}],
|
||||
"description": "Pull request state",
|
||||
},
|
||||
"assignee": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Assignee username",
|
||||
},
|
||||
"assignees": {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
||||
"description": "List of assignee usernames",
|
||||
},
|
||||
"labels": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "integer"}},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "List of label IDs",
|
||||
},
|
||||
"milestone": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "Milestone ID",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "merge_pull_request",
|
||||
"method_name": "merge_pull_request",
|
||||
"description": "Merge a pull request using specified merge method.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"description": "Merge method",
|
||||
"enum": ["merge", "rebase", "rebase-merge", "squash"],
|
||||
"default": "merge",
|
||||
},
|
||||
"title": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Merge commit title",
|
||||
},
|
||||
"message": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Merge commit message",
|
||||
},
|
||||
"delete_branch_after_merge": {
|
||||
"type": "boolean",
|
||||
"description": "Delete source branch after merge",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "close_pull_request",
|
||||
"method_name": "close_pull_request",
|
||||
"description": "Close a pull request without merging.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "reopen_pull_request",
|
||||
"method_name": "reopen_pull_request",
|
||||
"description": "Reopen a closed pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === PR DETAILS ===
|
||||
{
|
||||
"name": "list_pr_commits",
|
||||
"method_name": "list_pr_commits",
|
||||
"description": "List all commits in a pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_pr_files",
|
||||
"method_name": "list_pr_files",
|
||||
"description": "List all changed files in a pull request with additions/deletions count.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_pr_diff",
|
||||
"method_name": "get_pr_diff",
|
||||
"description": "Get the unified diff of a pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_pr_comments",
|
||||
"method_name": "list_pr_comments",
|
||||
"description": "List all comments on a pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_pr_comment",
|
||||
"method_name": "create_pr_comment",
|
||||
"description": "Add a comment to a pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Comment body (supports Markdown)",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number", "body"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === PR REVIEWS ===
|
||||
{
|
||||
"name": "list_pr_reviews",
|
||||
"method_name": "list_pr_reviews",
|
||||
"description": "List all reviews on a pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_pr_review",
|
||||
"method_name": "create_pr_review",
|
||||
"description": "Create a review for a pull request (approve, request changes, or comment).",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"event": {
|
||||
"type": "string",
|
||||
"description": "Review event type",
|
||||
"enum": ["APPROVED", "REQUEST_CHANGES", "COMMENT"],
|
||||
},
|
||||
"body": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Review comment (optional for APPROVED)",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number", "event"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "request_pr_reviewers",
|
||||
"method_name": "request_pr_reviewers",
|
||||
"description": "Request reviewers for a pull request.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"pr_number": {
|
||||
"type": "integer",
|
||||
"description": "Pull request number",
|
||||
"minimum": 1,
|
||||
},
|
||||
"reviewers": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "List of reviewer usernames",
|
||||
"minItems": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "pr_number", "reviewers"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_pull_requests(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
state: str = "open",
|
||||
sort: str = "created",
|
||||
labels: str | None = None,
|
||||
milestone: str | None = None,
|
||||
page: int = 1,
|
||||
limit: int = 30,
|
||||
) -> str:
|
||||
"""List repository pull requests"""
|
||||
params = {
|
||||
"state": state,
|
||||
"sort": sort,
|
||||
"labels": labels,
|
||||
"milestone": milestone,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
}
|
||||
prs = await client.list_pull_requests(owner, repo, params)
|
||||
result = {"success": True, "count": len(prs), "pull_requests": prs}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""Get pull request details"""
|
||||
pr = await client.get_pull_request(owner, repo, pr_number)
|
||||
result = {"success": True, "pull_request": pr}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_pull_request(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
title: str,
|
||||
head: str,
|
||||
base: str,
|
||||
body: str | None = None,
|
||||
assignee: str | None = None,
|
||||
assignees: list[str] | None = None,
|
||||
labels: list[int] | None = None,
|
||||
milestone: int | None = None,
|
||||
) -> str:
|
||||
"""Create a new pull request"""
|
||||
data = {
|
||||
"title": title,
|
||||
"head": head,
|
||||
"base": base,
|
||||
"body": body,
|
||||
"assignee": assignee,
|
||||
"assignees": assignees,
|
||||
"labels": labels,
|
||||
"milestone": milestone,
|
||||
}
|
||||
pr = await client.create_pull_request(owner, repo, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Pull request #{pr['number']} created successfully",
|
||||
"pull_request": pr,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def update_pull_request(
|
||||
client: GiteaClient, owner: str, repo: str, pr_number: int, **kwargs
|
||||
) -> str:
|
||||
"""Update a pull request"""
|
||||
# Build update data from kwargs
|
||||
data = {
|
||||
k: v for k, v in kwargs.items() if v is not None and k not in ["owner", "repo", "pr_number"]
|
||||
}
|
||||
|
||||
pr = await client.update_pull_request(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Pull request #{pr_number} updated successfully",
|
||||
"pull_request": pr,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def merge_pull_request(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
pr_number: int,
|
||||
method: str = "merge",
|
||||
title: str | None = None,
|
||||
message: str | None = None,
|
||||
delete_branch_after_merge: bool = False,
|
||||
) -> str:
|
||||
"""Merge a pull request"""
|
||||
data = {
|
||||
"Do": method,
|
||||
"MergeTitleField": title,
|
||||
"MergeMessageField": message,
|
||||
"delete_branch_after_merge": delete_branch_after_merge,
|
||||
}
|
||||
merge_result = await client.merge_pull_request(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Pull request #{pr_number} merged successfully using {method}",
|
||||
"result": merge_result,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def close_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""Close a pull request"""
|
||||
data = {"state": "closed"}
|
||||
pr = await client.update_pull_request(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Pull request #{pr_number} closed successfully",
|
||||
"pull_request": pr,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def reopen_pull_request(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""Reopen a pull request"""
|
||||
data = {"state": "open"}
|
||||
pr = await client.update_pull_request(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Pull request #{pr_number} reopened successfully",
|
||||
"pull_request": pr,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# PR Details
|
||||
async def list_pr_commits(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""List pull request commits"""
|
||||
commits = await client.list_pr_commits(owner, repo, pr_number)
|
||||
result = {"success": True, "count": len(commits), "commits": commits}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def list_pr_files(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""List pull request files"""
|
||||
files = await client.list_pr_files(owner, repo, pr_number)
|
||||
result = {"success": True, "count": len(files), "files": files}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_pr_diff(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""Get pull request diff"""
|
||||
diff = await client.get_pr_diff(owner, repo, pr_number)
|
||||
result = {"success": True, "diff": diff}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def list_pr_comments(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""List pull request comments"""
|
||||
# PR comments are same as issue comments in Gitea API
|
||||
comments = await client.list_issue_comments(owner, repo, pr_number)
|
||||
result = {"success": True, "count": len(comments), "comments": comments}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_pr_comment(
|
||||
client: GiteaClient, owner: str, repo: str, pr_number: int, body: str
|
||||
) -> str:
|
||||
"""Create pull request comment"""
|
||||
data = {"body": body}
|
||||
comment = await client.create_issue_comment(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Comment added to pull request #{pr_number}",
|
||||
"comment": comment,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# PR Reviews
|
||||
async def list_pr_reviews(client: GiteaClient, owner: str, repo: str, pr_number: int) -> str:
|
||||
"""List pull request reviews"""
|
||||
reviews = await client.list_pr_reviews(owner, repo, pr_number)
|
||||
result = {"success": True, "count": len(reviews), "reviews": reviews}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_pr_review(
|
||||
client: GiteaClient, owner: str, repo: str, pr_number: int, event: str, body: str | None = None
|
||||
) -> str:
|
||||
"""Create pull request review"""
|
||||
data = {"event": event, "body": body}
|
||||
review = await client.create_pr_review(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Review {event} added to pull request #{pr_number}",
|
||||
"review": review,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def request_pr_reviewers(
|
||||
client: GiteaClient, owner: str, repo: str, pr_number: int, reviewers: list[str]
|
||||
) -> str:
|
||||
"""Request pull request reviewers"""
|
||||
data = {"reviewers": reviewers}
|
||||
reviewers_result = await client.request_pr_reviewers(owner, repo, pr_number, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Reviewers requested for pull request #{pr_number}",
|
||||
"result": reviewers_result,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
709
plugins/gitea/handlers/repositories.py
Normal file
709
plugins/gitea/handlers/repositories.py
Normal file
@@ -0,0 +1,709 @@
|
||||
"""Repository Handler - manages Gitea repositories, branches, tags, and files"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.gitea.client import GiteaClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === REPOSITORIES ===
|
||||
{
|
||||
"name": "list_repositories",
|
||||
"method_name": "list_repositories",
|
||||
"description": "List Gitea repositories for a user/organization or current user. Returns repository list with metadata.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Repository owner username/org (null for current user repos)",
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Filter by repository type",
|
||||
"enum": ["all", "owner", "collaborative", "member"],
|
||||
"default": "all",
|
||||
},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_repository",
|
||||
"method_name": "get_repository",
|
||||
"description": "Get details of a specific Gitea repository. Returns complete repository information.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "Repository owner username/org",
|
||||
"minLength": 1,
|
||||
},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_repository",
|
||||
"method_name": "create_repository",
|
||||
"description": "Create a new Gitea repository. Can create user or organization repository with optional auto-initialization.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Repository name",
|
||||
"minLength": 1,
|
||||
"maxLength": 100,
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Repository description",
|
||||
"maxLength": 500,
|
||||
},
|
||||
"private": {
|
||||
"type": "boolean",
|
||||
"description": "Make repository private",
|
||||
"default": False,
|
||||
},
|
||||
"auto_init": {
|
||||
"type": "boolean",
|
||||
"description": "Initialize with README",
|
||||
"default": False,
|
||||
},
|
||||
"gitignores": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Gitignore template name",
|
||||
},
|
||||
"license": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "License template name",
|
||||
},
|
||||
"readme": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Readme template",
|
||||
},
|
||||
"default_branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Default branch name",
|
||||
},
|
||||
"org": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Create under organization (null for user repo)",
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_repository",
|
||||
"method_name": "update_repository",
|
||||
"description": "Update Gitea repository settings like name, description, visibility, and features.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "New repository name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Repository description",
|
||||
},
|
||||
"website": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Repository website",
|
||||
},
|
||||
"private": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Make repository private",
|
||||
},
|
||||
"archived": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Archive repository",
|
||||
},
|
||||
"has_issues": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable issues",
|
||||
},
|
||||
"has_wiki": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable wiki",
|
||||
},
|
||||
"default_branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Default branch",
|
||||
},
|
||||
"allow_merge_commits": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Allow merge commits",
|
||||
},
|
||||
"allow_rebase": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Allow rebase",
|
||||
},
|
||||
"allow_squash_merge": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Allow squash merge",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_repository",
|
||||
"method_name": "delete_repository",
|
||||
"description": "Delete a Gitea repository permanently. This action cannot be undone!",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# === BRANCHES ===
|
||||
{
|
||||
"name": "list_branches",
|
||||
"method_name": "list_branches",
|
||||
"description": "List all branches in a Gitea repository with commit information and protection status.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_branch",
|
||||
"method_name": "get_branch",
|
||||
"description": "Get details of a specific branch including latest commit and protection settings.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"branch": {"type": "string", "description": "Branch name", "minLength": 1},
|
||||
},
|
||||
"required": ["owner", "repo", "branch"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_branch",
|
||||
"method_name": "create_branch",
|
||||
"description": "Create a new branch in a Gitea repository from existing branch or commit.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"new_branch_name": {
|
||||
"type": "string",
|
||||
"description": "New branch name",
|
||||
"minLength": 1,
|
||||
},
|
||||
"old_branch_name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Source branch (default: default branch)",
|
||||
},
|
||||
"old_ref_name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Source commit SHA or ref",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "new_branch_name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_branch",
|
||||
"method_name": "delete_branch",
|
||||
"description": "Delete a branch from a Gitea repository. Cannot delete default branch.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "Branch name to delete",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "branch"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === TAGS ===
|
||||
{
|
||||
"name": "list_tags",
|
||||
"method_name": "list_tags",
|
||||
"description": "List all tags in a Gitea repository with commit information.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_tag",
|
||||
"method_name": "create_tag",
|
||||
"description": "Create a new tag in a Gitea repository at specific commit.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"tag_name": {"type": "string", "description": "Tag name", "minLength": 1},
|
||||
"message": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Tag message (annotated tag)",
|
||||
},
|
||||
"target": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Target commit SHA (default: latest)",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "tag_name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_tag",
|
||||
"method_name": "delete_tag",
|
||||
"description": "Delete a tag from a Gitea repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"tag": {"type": "string", "description": "Tag name to delete", "minLength": 1},
|
||||
},
|
||||
"required": ["owner", "repo", "tag"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === FILES ===
|
||||
{
|
||||
"name": "get_file",
|
||||
"method_name": "get_file",
|
||||
"description": "Get file contents from a Gitea repository. Returns file content (Base64 encoded) and metadata.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path in repository",
|
||||
"minLength": 1,
|
||||
},
|
||||
"ref": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Branch/tag/commit (default: default branch)",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "path"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_file",
|
||||
"method_name": "create_file",
|
||||
"description": "Create a new file in a Gitea repository with commit message.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path to create",
|
||||
"minLength": 1,
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "File content (will be Base64 encoded automatically unless content_is_base64=true)",
|
||||
},
|
||||
"message": {"type": "string", "description": "Commit message", "minLength": 1},
|
||||
"branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Target branch (default: default branch)",
|
||||
},
|
||||
"new_branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Create new branch for this commit",
|
||||
},
|
||||
"author_name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Author name",
|
||||
},
|
||||
"author_email": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Author email",
|
||||
},
|
||||
"content_is_base64": {
|
||||
"type": "boolean",
|
||||
"description": "Set to true if content is already Base64 encoded (skip automatic encoding)",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "path", "content", "message"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_file",
|
||||
"method_name": "update_file",
|
||||
"description": "Update an existing file in a Gitea repository. Requires current file SHA for conflict detection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path to update",
|
||||
"minLength": 1,
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "New file content (will be Base64 encoded automatically)",
|
||||
},
|
||||
"sha": {
|
||||
"type": "string",
|
||||
"description": "Current file SHA (for conflict detection)",
|
||||
"minLength": 1,
|
||||
},
|
||||
"message": {"type": "string", "description": "Commit message", "minLength": 1},
|
||||
"branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Target branch (default: default branch)",
|
||||
},
|
||||
"new_branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Create new branch for this commit",
|
||||
},
|
||||
"content_is_base64": {
|
||||
"type": "boolean",
|
||||
"description": "Set to true if content is already Base64 encoded (skip automatic encoding)",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "path", "content", "sha", "message"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_file",
|
||||
"method_name": "delete_file",
|
||||
"description": "Delete a file from a Gitea repository. Requires current file SHA for conflict detection.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path to delete",
|
||||
"minLength": 1,
|
||||
},
|
||||
"sha": {
|
||||
"type": "string",
|
||||
"description": "Current file SHA (for conflict detection)",
|
||||
"minLength": 1,
|
||||
},
|
||||
"message": {"type": "string", "description": "Commit message", "minLength": 1},
|
||||
"branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Target branch (default: default branch)",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "path", "sha", "message"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_repositories(
|
||||
client: GiteaClient, owner: str | None = None, type: str = "all", page: int = 1, limit: int = 30
|
||||
) -> str:
|
||||
"""List Gitea repositories"""
|
||||
repos = await client.list_repositories(owner=owner, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(repos), "repositories": repos}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_repository(client: GiteaClient, owner: str, repo: str) -> str:
|
||||
"""Get repository details"""
|
||||
repository = await client.get_repository(owner, repo)
|
||||
result = {"success": True, "repository": repository}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_repository(
|
||||
client: GiteaClient,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
private: bool = False,
|
||||
auto_init: bool = False,
|
||||
gitignores: str | None = None,
|
||||
license: str | None = None,
|
||||
readme: str | None = None,
|
||||
default_branch: str | None = None,
|
||||
org: str | None = None,
|
||||
) -> str:
|
||||
"""Create a new repository"""
|
||||
data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"private": private,
|
||||
"auto_init": auto_init,
|
||||
"gitignores": gitignores,
|
||||
"license": license,
|
||||
"readme": readme,
|
||||
"default_branch": default_branch,
|
||||
}
|
||||
repository = await client.create_repository(data, org=org)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Repository '{name}' created successfully",
|
||||
"repository": repository,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def update_repository(client: GiteaClient, owner: str, repo: str, **kwargs) -> str:
|
||||
"""Update repository settings"""
|
||||
# Build update data from kwargs
|
||||
data = {k: v for k, v in kwargs.items() if v is not None and k not in ["owner", "repo"]}
|
||||
|
||||
repository = await client.update_repository(owner, repo, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Repository '{owner}/{repo}' updated successfully",
|
||||
"repository": repository,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def delete_repository(client: GiteaClient, owner: str, repo: str) -> str:
|
||||
"""Delete a repository"""
|
||||
await client.delete_repository(owner, repo)
|
||||
result = {"success": True, "message": f"Repository '{owner}/{repo}' deleted successfully"}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Branch operations
|
||||
async def list_branches(
|
||||
client: GiteaClient, owner: str, repo: str, page: int = 1, limit: int = 30
|
||||
) -> str:
|
||||
"""List repository branches"""
|
||||
branches = await client.list_branches(owner, repo, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(branches), "branches": branches}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_branch(client: GiteaClient, owner: str, repo: str, branch: str) -> str:
|
||||
"""Get branch details"""
|
||||
branch_info = await client.get_branch(owner, repo, branch)
|
||||
result = {"success": True, "branch": branch_info}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_branch(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
new_branch_name: str,
|
||||
old_branch_name: str | None = None,
|
||||
old_ref_name: str | None = None,
|
||||
) -> str:
|
||||
"""Create a new branch"""
|
||||
data = {
|
||||
"new_branch_name": new_branch_name,
|
||||
"old_branch_name": old_branch_name,
|
||||
"old_ref_name": old_ref_name,
|
||||
}
|
||||
branch = await client.create_branch(owner, repo, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Branch '{new_branch_name}' created successfully",
|
||||
"branch": branch,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def delete_branch(client: GiteaClient, owner: str, repo: str, branch: str) -> str:
|
||||
"""Delete a branch"""
|
||||
await client.delete_branch(owner, repo, branch)
|
||||
result = {"success": True, "message": f"Branch '{branch}' deleted successfully"}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Tag operations
|
||||
async def list_tags(
|
||||
client: GiteaClient, owner: str, repo: str, page: int = 1, limit: int = 30
|
||||
) -> str:
|
||||
"""List repository tags"""
|
||||
tags = await client.list_tags(owner, repo, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(tags), "tags": tags}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_tag(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
tag_name: str,
|
||||
message: str | None = None,
|
||||
target: str | None = None,
|
||||
) -> str:
|
||||
"""Create a new tag"""
|
||||
data = {"tag_name": tag_name, "message": message, "target": target}
|
||||
tag = await client.create_tag(owner, repo, data)
|
||||
result = {"success": True, "message": f"Tag '{tag_name}' created successfully", "tag": tag}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def delete_tag(client: GiteaClient, owner: str, repo: str, tag: str) -> str:
|
||||
"""Delete a tag"""
|
||||
await client.delete_tag(owner, repo, tag)
|
||||
result = {"success": True, "message": f"Tag '{tag}' deleted successfully"}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# File operations
|
||||
async def get_file(
|
||||
client: GiteaClient, owner: str, repo: str, path: str, ref: str | None = None
|
||||
) -> str:
|
||||
"""Get file contents"""
|
||||
file_data = await client.get_file(owner, repo, path, ref=ref)
|
||||
result = {"success": True, "file": file_data}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_file(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
path: str,
|
||||
content: str,
|
||||
message: str,
|
||||
branch: str | None = None,
|
||||
new_branch: str | None = None,
|
||||
author_name: str | None = None,
|
||||
author_email: str | None = None,
|
||||
content_is_base64: bool = False,
|
||||
) -> str:
|
||||
"""Create a new file"""
|
||||
data = {
|
||||
"content": content,
|
||||
"message": message,
|
||||
"branch": branch,
|
||||
"new_branch": new_branch,
|
||||
"content_is_base64": content_is_base64,
|
||||
"author": {},
|
||||
}
|
||||
|
||||
if author_name:
|
||||
data["author"]["name"] = author_name
|
||||
if author_email:
|
||||
data["author"]["email"] = author_email
|
||||
|
||||
file_result = await client.create_file(owner, repo, path, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"File '{path}' created successfully",
|
||||
"file": file_result,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def update_file(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
path: str,
|
||||
content: str,
|
||||
sha: str,
|
||||
message: str,
|
||||
branch: str | None = None,
|
||||
new_branch: str | None = None,
|
||||
content_is_base64: bool = False,
|
||||
) -> str:
|
||||
"""Update an existing file"""
|
||||
data = {
|
||||
"content": content,
|
||||
"sha": sha,
|
||||
"message": message,
|
||||
"branch": branch,
|
||||
"new_branch": new_branch,
|
||||
"content_is_base64": content_is_base64,
|
||||
}
|
||||
|
||||
file_result = await client.update_file(owner, repo, path, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"File '{path}' updated successfully",
|
||||
"file": file_result,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def delete_file(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
path: str,
|
||||
sha: str,
|
||||
message: str,
|
||||
branch: str | None = None,
|
||||
) -> str:
|
||||
"""Delete a file from repository"""
|
||||
await client.delete_file(owner, repo, path, sha, message, branch)
|
||||
result = {"success": True, "message": f"File '{path}' deleted successfully"}
|
||||
return json.dumps(result, indent=2)
|
||||
245
plugins/gitea/handlers/users.py
Normal file
245
plugins/gitea/handlers/users.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""User & Organization Handler - manages Gitea users, organizations, and teams"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.gitea.client import GiteaClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === USERS ===
|
||||
{
|
||||
"name": "get_user",
|
||||
"method_name": "get_user",
|
||||
"description": "Get information about a Gitea user by username.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"description": "Username to look up",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["username"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_user_repos",
|
||||
"method_name": "list_user_repos",
|
||||
"description": "List all public repositories of a Gitea user.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {"type": "string", "description": "Username", "minLength": 1},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["username"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "search_users",
|
||||
"method_name": "search_users",
|
||||
"description": "Search for Gitea users by query or user ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"q": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Search query (username or full name)",
|
||||
},
|
||||
"uid": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "User ID to search for",
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === ORGANIZATIONS ===
|
||||
{
|
||||
"name": "list_organizations",
|
||||
"method_name": "list_organizations",
|
||||
"description": "List organizations for the current authenticated user.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_organization",
|
||||
"method_name": "get_organization",
|
||||
"description": "Get information about a Gitea organization by name.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"org": {"type": "string", "description": "Organization name", "minLength": 1}
|
||||
},
|
||||
"required": ["org"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_org_repos",
|
||||
"method_name": "list_org_repos",
|
||||
"description": "List all repositories of an organization.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"org": {"type": "string", "description": "Organization name", "minLength": 1},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["org"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === TEAMS ===
|
||||
{
|
||||
"name": "list_org_teams",
|
||||
"method_name": "list_org_teams",
|
||||
"description": "List all teams in an organization.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"org": {"type": "string", "description": "Organization name", "minLength": 1},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["org"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_team_members",
|
||||
"method_name": "list_team_members",
|
||||
"description": "List all members of a team.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"team_id": {"type": "integer", "description": "Team ID", "minimum": 1},
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Items per page (1-100)",
|
||||
"default": 30,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["team_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
async def get_user(client: GiteaClient, username: str) -> str:
|
||||
"""Get user information"""
|
||||
user = await client.get_user(username)
|
||||
result = {"success": True, "user": user}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def list_user_repos(
|
||||
client: GiteaClient, username: str, page: int = 1, limit: int = 30
|
||||
) -> str:
|
||||
"""List user repositories"""
|
||||
repos = await client.list_user_repos(username, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(repos), "repositories": repos}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def search_users(client: GiteaClient, q: str | None = None, uid: int | None = None) -> str:
|
||||
"""Search users"""
|
||||
users = await client.search_users(query=q, uid=uid)
|
||||
result = {"success": True, "count": len(users), "users": users}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Organization operations
|
||||
async def list_organizations(client: GiteaClient, page: int = 1, limit: int = 30) -> str:
|
||||
"""List organizations"""
|
||||
orgs = await client.list_organizations(page=page, limit=limit)
|
||||
result = {"success": True, "count": len(orgs), "organizations": orgs}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_organization(client: GiteaClient, org: str) -> str:
|
||||
"""Get organization information"""
|
||||
organization = await client.get_organization(org)
|
||||
result = {"success": True, "organization": organization}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def list_org_repos(client: GiteaClient, org: str, page: int = 1, limit: int = 30) -> str:
|
||||
"""List organization repositories"""
|
||||
repos = await client.list_org_repos(org, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(repos), "repositories": repos}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
# Team operations
|
||||
async def list_org_teams(client: GiteaClient, org: str, page: int = 1, limit: int = 30) -> str:
|
||||
"""List organization teams"""
|
||||
teams = await client.list_org_teams(org, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(teams), "teams": teams}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def list_team_members(
|
||||
client: GiteaClient, team_id: int, page: int = 1, limit: int = 30
|
||||
) -> str:
|
||||
"""List team members"""
|
||||
members = await client.list_team_members(team_id, page=page, limit=limit)
|
||||
result = {"success": True, "count": len(members), "members": members}
|
||||
return json.dumps(result, indent=2)
|
||||
216
plugins/gitea/handlers/webhooks.py
Normal file
216
plugins/gitea/handlers/webhooks.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""Webhook Handler - manages Gitea webhooks for repositories"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.gitea.client import GiteaClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === WEBHOOKS ===
|
||||
{
|
||||
"name": "list_webhooks",
|
||||
"method_name": "list_webhooks",
|
||||
"description": "List all webhooks configured for a repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
},
|
||||
"required": ["owner", "repo"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_webhook",
|
||||
"method_name": "create_webhook",
|
||||
"description": "Create a new webhook for a repository to receive event notifications.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Webhook URL to send events to",
|
||||
"format": "uri",
|
||||
},
|
||||
"events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"create",
|
||||
"delete",
|
||||
"fork",
|
||||
"push",
|
||||
"issues",
|
||||
"issue_assign",
|
||||
"issue_label",
|
||||
"issue_milestone",
|
||||
"issue_comment",
|
||||
"pull_request",
|
||||
"pull_request_assign",
|
||||
"pull_request_label",
|
||||
"pull_request_milestone",
|
||||
"pull_request_comment",
|
||||
"pull_request_review_approved",
|
||||
"pull_request_review_rejected",
|
||||
"pull_request_review_comment",
|
||||
"pull_request_sync",
|
||||
"wiki",
|
||||
"repository",
|
||||
"release",
|
||||
],
|
||||
},
|
||||
"description": "List of events to trigger webhook",
|
||||
"minItems": 1,
|
||||
},
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"description": "Content type for webhook payload",
|
||||
"enum": ["json", "form"],
|
||||
"default": "json",
|
||||
},
|
||||
"secret": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Secret for webhook signature verification",
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "Activate webhook immediately",
|
||||
"default": True,
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Webhook type",
|
||||
"enum": [
|
||||
"gitea",
|
||||
"gogs",
|
||||
"slack",
|
||||
"discord",
|
||||
"dingtalk",
|
||||
"telegram",
|
||||
"msteams",
|
||||
"feishu",
|
||||
"wechatwork",
|
||||
"packagist",
|
||||
],
|
||||
"default": "gitea",
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "url", "events"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_webhook",
|
||||
"method_name": "delete_webhook",
|
||||
"description": "Delete a webhook from a repository.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"webhook_id": {
|
||||
"type": "integer",
|
||||
"description": "Webhook ID to delete",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "webhook_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "test_webhook",
|
||||
"method_name": "test_webhook",
|
||||
"description": "Send a test payload to a webhook to verify it's working.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"webhook_id": {
|
||||
"type": "integer",
|
||||
"description": "Webhook ID to test",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["owner", "repo", "webhook_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "get_webhook",
|
||||
"method_name": "get_webhook",
|
||||
"description": "Get details of a specific webhook.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||
"webhook_id": {"type": "integer", "description": "Webhook ID", "minimum": 1},
|
||||
},
|
||||
"required": ["owner", "repo", "webhook_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_webhooks(client: GiteaClient, owner: str, repo: str) -> str:
|
||||
"""List repository webhooks"""
|
||||
webhooks = await client.list_webhooks(owner, repo)
|
||||
result = {"success": True, "count": len(webhooks), "webhooks": webhooks}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def create_webhook(
|
||||
client: GiteaClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
url: str,
|
||||
events: list[str],
|
||||
content_type: str = "json",
|
||||
secret: str | None = None,
|
||||
active: bool = True,
|
||||
type: str = "gitea",
|
||||
) -> str:
|
||||
"""Create a webhook"""
|
||||
# Build webhook configuration
|
||||
config = {"url": url, "content_type": content_type}
|
||||
if secret:
|
||||
config["secret"] = secret
|
||||
|
||||
data = {"type": type, "config": config, "events": events, "active": active}
|
||||
|
||||
webhook = await client.create_webhook(owner, repo, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Webhook created successfully for {owner}/{repo}",
|
||||
"webhook": webhook,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def delete_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: int) -> str:
|
||||
"""Delete a webhook"""
|
||||
await client.delete_webhook(owner, repo, webhook_id)
|
||||
result = {"success": True, "message": f"Webhook {webhook_id} deleted successfully"}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def test_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: int) -> str:
|
||||
"""Test a webhook"""
|
||||
test_result = await client.test_webhook(owner, repo, webhook_id)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Test payload sent to webhook {webhook_id}",
|
||||
"result": test_result,
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
async def get_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: int) -> str:
|
||||
"""Get webhook details"""
|
||||
webhook = await client.get_webhook(owner, repo, webhook_id)
|
||||
result = {"success": True, "webhook": webhook}
|
||||
return json.dumps(result, indent=2)
|
||||
161
plugins/gitea/plugin.py
Normal file
161
plugins/gitea/plugin.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
Gitea Plugin - Clean Architecture
|
||||
|
||||
Complete Gitea management through REST API with OAuth support.
|
||||
Modular handlers for better organization and maintainability.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from plugins.base import BasePlugin
|
||||
from plugins.gitea import handlers
|
||||
from plugins.gitea.client import GiteaClient
|
||||
|
||||
class GiteaPlugin(BasePlugin):
|
||||
"""
|
||||
Gitea project plugin - Clean architecture.
|
||||
|
||||
Provides comprehensive Gitea management capabilities including:
|
||||
- Repository management (CRUD, branches, tags, files)
|
||||
- Issue tracking (issues, labels, milestones, comments)
|
||||
- Pull requests (PRs, reviews, merges)
|
||||
- User and organization management
|
||||
- Webhook configuration
|
||||
- OAuth integration
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_plugin_name() -> str:
|
||||
"""Return plugin type identifier"""
|
||||
return "gitea"
|
||||
|
||||
@staticmethod
|
||||
def get_required_config_keys() -> list[str]:
|
||||
"""Return required configuration keys"""
|
||||
return ["url"] # Token is optional (can use OAuth)
|
||||
|
||||
def __init__(self, config: dict[str, Any], project_id: str | None = None):
|
||||
"""
|
||||
Initialize Gitea plugin with handlers.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary containing:
|
||||
- url: Gitea instance URL
|
||||
- token: (Optional) Personal access token
|
||||
- oauth_enabled: (Optional) Whether OAuth is enabled
|
||||
project_id: Optional project ID (auto-generated if not provided)
|
||||
"""
|
||||
super().__init__(config, project_id=project_id)
|
||||
|
||||
# Create Gitea API client
|
||||
self.client = GiteaClient(
|
||||
site_url=config["url"],
|
||||
token=config.get("token"),
|
||||
oauth_enabled=config.get("oauth_enabled", False),
|
||||
)
|
||||
|
||||
# No handler instances needed in Option B architecture
|
||||
# Tools are delegated directly to handler functions
|
||||
|
||||
@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 = []
|
||||
|
||||
# Collect specifications from all handlers
|
||||
specs.extend(handlers.repositories.get_tool_specifications())
|
||||
specs.extend(handlers.issues.get_tool_specifications())
|
||||
specs.extend(handlers.pull_requests.get_tool_specifications())
|
||||
specs.extend(handlers.users.get_tool_specifications())
|
||||
specs.extend(handlers.webhooks.get_tool_specifications())
|
||||
|
||||
return specs
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""
|
||||
Dynamically delegate method calls to appropriate handlers.
|
||||
|
||||
This allows ToolGenerator to call methods like plugin.list_repositories()
|
||||
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 handlers modules
|
||||
try:
|
||||
# Check repositories handlers
|
||||
if hasattr(handlers.repositories, name):
|
||||
func = getattr(handlers.repositories, name)
|
||||
|
||||
# Create wrapper that passes self.client
|
||||
async def wrapper(**kwargs):
|
||||
return await func(self.client, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
# Check issues handlers
|
||||
if hasattr(handlers.issues, name):
|
||||
func = getattr(handlers.issues, name)
|
||||
|
||||
async def wrapper(**kwargs):
|
||||
return await func(self.client, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
# Check pull_requests handlers
|
||||
if hasattr(handlers.pull_requests, name):
|
||||
func = getattr(handlers.pull_requests, name)
|
||||
|
||||
async def wrapper(**kwargs):
|
||||
return await func(self.client, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
# Check users handlers
|
||||
if hasattr(handlers.users, name):
|
||||
func = getattr(handlers.users, name)
|
||||
|
||||
async def wrapper(**kwargs):
|
||||
return await func(self.client, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
# Check webhooks handlers
|
||||
if hasattr(handlers.webhooks, name):
|
||||
func = getattr(handlers.webhooks, name)
|
||||
|
||||
async def wrapper(**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}'")
|
||||
|
||||
except AttributeError:
|
||||
raise
|
||||
|
||||
async def health_check(self) -> dict[str, Any]:
|
||||
"""
|
||||
Check if Gitea instance is accessible.
|
||||
|
||||
Returns:
|
||||
Dict containing health check result
|
||||
"""
|
||||
try:
|
||||
# Try to get user information (requires authentication)
|
||||
await self.client.request("GET", "user")
|
||||
return {"healthy": True, "message": "Gitea instance is accessible"}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "message": f"Gitea health check failed: {str(e)}"}
|
||||
127
plugins/gitea/schemas/__init__.py
Normal file
127
plugins/gitea/schemas/__init__.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Gitea Plugin Pydantic Schemas
|
||||
|
||||
All validation schemas for Gitea plugin operations.
|
||||
"""
|
||||
|
||||
from .common import (
|
||||
ErrorResponse,
|
||||
GiteaPermissions,
|
||||
GiteaTimestamps,
|
||||
GiteaUser,
|
||||
PaginationParams,
|
||||
Site,
|
||||
SuccessResponse,
|
||||
)
|
||||
from .issue import (
|
||||
Comment,
|
||||
CreateCommentRequest,
|
||||
CreateIssueRequest,
|
||||
CreateLabelRequest,
|
||||
CreateMilestoneRequest,
|
||||
Issue,
|
||||
IssueListFilters,
|
||||
Label,
|
||||
Milestone,
|
||||
UpdateIssueRequest,
|
||||
)
|
||||
from .pull_request import (
|
||||
CreatePullRequestRequest,
|
||||
CreateReviewRequest,
|
||||
MergePullRequestRequest,
|
||||
PRBranchInfo,
|
||||
PRCommit,
|
||||
PRFile,
|
||||
PRListFilters,
|
||||
PRReview,
|
||||
PullRequest,
|
||||
RequestReviewersRequest,
|
||||
UpdatePullRequestRequest,
|
||||
)
|
||||
from .repository import (
|
||||
Branch,
|
||||
CreateBranchRequest,
|
||||
CreateFileRequest,
|
||||
CreateRepositoryRequest,
|
||||
CreateTagRequest,
|
||||
FileContent,
|
||||
Repository,
|
||||
Tag,
|
||||
UpdateFileRequest,
|
||||
UpdateRepositoryRequest,
|
||||
)
|
||||
from .user import (
|
||||
Email,
|
||||
Organization,
|
||||
SearchOrgsRequest,
|
||||
SearchUsersRequest,
|
||||
Team,
|
||||
TeamMember,
|
||||
User,
|
||||
)
|
||||
from .webhook import (
|
||||
CreateWebhookRequest,
|
||||
UpdateWebhookRequest,
|
||||
Webhook,
|
||||
WebhookConfig,
|
||||
WebhookTestResult,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Common
|
||||
"Site",
|
||||
"PaginationParams",
|
||||
"ErrorResponse",
|
||||
"SuccessResponse",
|
||||
"GiteaUser",
|
||||
"GiteaPermissions",
|
||||
"GiteaTimestamps",
|
||||
# Repository
|
||||
"Repository",
|
||||
"Branch",
|
||||
"Tag",
|
||||
"FileContent",
|
||||
"CreateRepositoryRequest",
|
||||
"UpdateRepositoryRequest",
|
||||
"CreateBranchRequest",
|
||||
"CreateTagRequest",
|
||||
"CreateFileRequest",
|
||||
"UpdateFileRequest",
|
||||
# Issue
|
||||
"Label",
|
||||
"Milestone",
|
||||
"Issue",
|
||||
"Comment",
|
||||
"CreateIssueRequest",
|
||||
"UpdateIssueRequest",
|
||||
"CreateCommentRequest",
|
||||
"CreateLabelRequest",
|
||||
"CreateMilestoneRequest",
|
||||
"IssueListFilters",
|
||||
# Pull Request
|
||||
"PRBranchInfo",
|
||||
"PullRequest",
|
||||
"PRReview",
|
||||
"PRCommit",
|
||||
"PRFile",
|
||||
"CreatePullRequestRequest",
|
||||
"UpdatePullRequestRequest",
|
||||
"MergePullRequestRequest",
|
||||
"CreateReviewRequest",
|
||||
"RequestReviewersRequest",
|
||||
"PRListFilters",
|
||||
# User
|
||||
"User",
|
||||
"Organization",
|
||||
"Team",
|
||||
"TeamMember",
|
||||
"Email",
|
||||
"SearchUsersRequest",
|
||||
"SearchOrgsRequest",
|
||||
# Webhook
|
||||
"Webhook",
|
||||
"WebhookConfig",
|
||||
"CreateWebhookRequest",
|
||||
"UpdateWebhookRequest",
|
||||
"WebhookTestResult",
|
||||
]
|
||||
73
plugins/gitea/schemas/common.py
Normal file
73
plugins/gitea/schemas/common.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Common Pydantic Schemas for Gitea Plugin
|
||||
|
||||
Shared validation schemas used across Gitea handlers.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
class Site(BaseModel):
|
||||
"""Gitea site configuration"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
site_id: str = Field(..., description="Site identifier (e.g., 'site1')")
|
||||
url: str = Field(..., description="Gitea instance URL")
|
||||
token: str | None = Field(None, description="Personal access token")
|
||||
alias: str | None = Field(None, description="Site alias")
|
||||
oauth_enabled: bool = Field(default=False, description="Whether OAuth is enabled")
|
||||
|
||||
class PaginationParams(BaseModel):
|
||||
"""Pagination parameters for list endpoints"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
page: int = Field(default=1, ge=1, description="Page number (starts at 1)")
|
||||
limit: int = Field(default=30, ge=1, le=100, description="Number of items per page (1-100)")
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error response"""
|
||||
|
||||
error: bool = Field(default=True)
|
||||
message: str = Field(..., description="Error message")
|
||||
code: str | None = Field(None, description="Error code")
|
||||
details: dict[str, Any] | None = Field(None, description="Additional error details")
|
||||
|
||||
class SuccessResponse(BaseModel):
|
||||
"""Standard success response"""
|
||||
|
||||
success: bool = Field(default=True)
|
||||
message: str = Field(..., description="Success message")
|
||||
data: dict[str, Any] | None = Field(None, description="Response data")
|
||||
|
||||
class GiteaUser(BaseModel):
|
||||
"""Gitea user information"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
login: str
|
||||
full_name: str | None = None
|
||||
email: str | None = None
|
||||
avatar_url: str | None = None
|
||||
is_admin: bool = False
|
||||
|
||||
class GiteaPermissions(BaseModel):
|
||||
"""Repository permissions"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
admin: bool = False
|
||||
push: bool = False
|
||||
pull: bool = False
|
||||
|
||||
class GiteaTimestamps(BaseModel):
|
||||
"""Common timestamp fields"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
184
plugins/gitea/schemas/issue.py
Normal file
184
plugins/gitea/schemas/issue.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
Issue Pydantic Schemas
|
||||
|
||||
Validation schemas for Gitea issue operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from .common import GiteaUser
|
||||
|
||||
class Label(BaseModel):
|
||||
"""Issue/PR label"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
name: str
|
||||
color: str
|
||||
description: str | None = None
|
||||
url: str | None = None
|
||||
|
||||
class Milestone(BaseModel):
|
||||
"""Issue/PR milestone"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
title: str
|
||||
description: str | None = None
|
||||
state: str # "open" or "closed"
|
||||
open_issues: int = 0
|
||||
closed_issues: int = 0
|
||||
created_at: datetime
|
||||
updated_at: datetime | None = None
|
||||
closed_at: datetime | None = None
|
||||
due_on: datetime | None = None
|
||||
|
||||
class Issue(BaseModel):
|
||||
"""Gitea issue model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
number: int
|
||||
user: GiteaUser
|
||||
original_author: str | None = None
|
||||
original_author_id: int | None = None
|
||||
title: str
|
||||
body: str | None = None
|
||||
ref: str | None = None
|
||||
labels: list[Label] = []
|
||||
milestone: Milestone | None = None
|
||||
assignee: GiteaUser | None = None
|
||||
assignees: list[GiteaUser] = []
|
||||
state: str # "open" or "closed"
|
||||
is_locked: bool = False
|
||||
comments: int = 0
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
closed_at: datetime | None = None
|
||||
due_date: datetime | None = None
|
||||
pull_request: dict | None = None # Non-null if this is a PR
|
||||
repository: dict | None = None
|
||||
html_url: str
|
||||
url: str
|
||||
|
||||
class Comment(BaseModel):
|
||||
"""Issue/PR comment"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
html_url: str
|
||||
pull_request_url: str | None = None
|
||||
issue_url: str | None = None
|
||||
user: GiteaUser
|
||||
original_author: str | None = None
|
||||
original_author_id: int | None = None
|
||||
body: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class CreateIssueRequest(BaseModel):
|
||||
"""Request to create an issue"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
title: str = Field(..., min_length=1, max_length=255, description="Issue title")
|
||||
body: str | None = Field(None, description="Issue body/description")
|
||||
assignee: str | None = Field(None, description="Username of assignee")
|
||||
assignees: list[str] | None = Field(None, description="List of assignee usernames")
|
||||
closed: bool | None = Field(False, description="Create as closed")
|
||||
due_date: datetime | None = Field(None, description="Due date")
|
||||
labels: list[int] | None = Field(None, description="List of label IDs")
|
||||
milestone: int | None = Field(None, description="Milestone ID")
|
||||
ref: str | None = Field(None, description="Issue ref")
|
||||
|
||||
class UpdateIssueRequest(BaseModel):
|
||||
"""Request to update an issue"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
title: str | None = Field(None, min_length=1, max_length=255, description="Issue title")
|
||||
body: str | None = Field(None, description="Issue body/description")
|
||||
assignee: str | None = Field(None, description="Username of assignee")
|
||||
assignees: list[str] | None = Field(None, description="List of assignee usernames")
|
||||
state: str | None = Field(None, description="State (open/closed)")
|
||||
due_date: datetime | None = Field(None, description="Due date")
|
||||
labels: list[int] | None = Field(None, description="List of label IDs")
|
||||
milestone: int | None = Field(None, description="Milestone ID")
|
||||
ref: str | None = Field(None, description="Issue ref")
|
||||
unset_due_date: bool | None = Field(None, description="Unset due date")
|
||||
|
||||
@classmethod
|
||||
@field_validator("state")
|
||||
def validate_state(cls, v):
|
||||
if v and v not in ["open", "closed"]:
|
||||
raise ValueError("State must be 'open' or 'closed'")
|
||||
return v
|
||||
|
||||
class CreateCommentRequest(BaseModel):
|
||||
"""Request to create a comment"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
body: str = Field(..., min_length=1, description="Comment body")
|
||||
|
||||
class CreateLabelRequest(BaseModel):
|
||||
"""Request to create a label"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=50, description="Label name")
|
||||
color: str = Field(..., pattern=r"^[0-9A-Fa-f]{6}$", description="Label color (hex without #)")
|
||||
description: str | None = Field(None, max_length=200, description="Label description")
|
||||
|
||||
class CreateMilestoneRequest(BaseModel):
|
||||
"""Request to create a milestone"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
title: str = Field(..., min_length=1, max_length=255, description="Milestone title")
|
||||
description: str | None = Field(None, description="Milestone description")
|
||||
due_on: datetime | None = Field(None, description="Due date")
|
||||
state: str | None = Field("open", description="State (open/closed)")
|
||||
|
||||
@classmethod
|
||||
@field_validator("state")
|
||||
def validate_state(cls, v):
|
||||
if v not in ["open", "closed"]:
|
||||
raise ValueError("State must be 'open' or 'closed'")
|
||||
return v
|
||||
|
||||
class IssueListFilters(BaseModel):
|
||||
"""Filters for listing issues"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
state: str | None = Field("open", description="Filter by state (open/closed/all)")
|
||||
labels: str | None = Field(None, description="Comma-separated label IDs")
|
||||
q: str | None = Field(None, description="Search query")
|
||||
type: str | None = Field(None, description="Filter by type (issues/pulls)")
|
||||
milestones: str | None = Field(None, description="Comma-separated milestone names")
|
||||
since: datetime | None = Field(None, description="Only show items updated after this time")
|
||||
before: datetime | None = Field(None, description="Only show items updated before this time")
|
||||
created_by: str | None = Field(None, description="Filter by creator username")
|
||||
assigned_by: str | None = Field(None, description="Filter by assignee username")
|
||||
mentioned_by: str | None = Field(None, description="Filter by mentioned username")
|
||||
|
||||
@classmethod
|
||||
@field_validator("state")
|
||||
def validate_state(cls, v):
|
||||
if v and v not in ["open", "closed", "all"]:
|
||||
raise ValueError("State must be 'open', 'closed', or 'all'")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("type")
|
||||
def validate_type(cls, v):
|
||||
if v and v not in ["issues", "pulls"]:
|
||||
raise ValueError("Type must be 'issues' or 'pulls'")
|
||||
return v
|
||||
215
plugins/gitea/schemas/pull_request.py
Normal file
215
plugins/gitea/schemas/pull_request.py
Normal file
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
Pull Request Pydantic Schemas
|
||||
|
||||
Validation schemas for Gitea pull request operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from .common import GiteaUser
|
||||
from .issue import Label, Milestone
|
||||
|
||||
class PRBranchInfo(BaseModel):
|
||||
"""Pull request branch information"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
label: str
|
||||
ref: str
|
||||
sha: str
|
||||
repo_id: int
|
||||
repository: dict | None = None
|
||||
|
||||
class PullRequest(BaseModel):
|
||||
"""Gitea pull request model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
number: int
|
||||
user: GiteaUser
|
||||
title: str
|
||||
body: str | None = None
|
||||
labels: list[Label] = []
|
||||
milestone: Milestone | None = None
|
||||
assignee: GiteaUser | None = None
|
||||
assignees: list[GiteaUser] = []
|
||||
requested_reviewers: list[GiteaUser] = []
|
||||
state: str # "open" or "closed"
|
||||
is_locked: bool = False
|
||||
comments: int = 0
|
||||
html_url: str
|
||||
diff_url: str
|
||||
patch_url: str
|
||||
mergeable: bool = False
|
||||
merged: bool = False
|
||||
merged_at: datetime | None = None
|
||||
merge_commit_sha: str | None = None
|
||||
merged_by: GiteaUser | None = None
|
||||
base: PRBranchInfo
|
||||
head: PRBranchInfo
|
||||
merge_base: str | None = None
|
||||
due_date: datetime | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
closed_at: datetime | None = None
|
||||
allow_maintainer_edit: bool = False
|
||||
url: str
|
||||
|
||||
class PRReview(BaseModel):
|
||||
"""Pull request review"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
user: GiteaUser
|
||||
body: str | None = None
|
||||
commit_id: str
|
||||
state: str # "PENDING", "APPROVED", "REQUEST_CHANGES", "COMMENT"
|
||||
html_url: str
|
||||
pull_request_url: str
|
||||
submitted_at: datetime
|
||||
|
||||
class PRCommit(BaseModel):
|
||||
"""Pull request commit"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
sha: str
|
||||
commit: dict
|
||||
url: str
|
||||
html_url: str
|
||||
comments_url: str
|
||||
author: GiteaUser | None = None
|
||||
committer: GiteaUser | None = None
|
||||
parents: list[dict] = []
|
||||
|
||||
class PRFile(BaseModel):
|
||||
"""Pull request file change"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
filename: str
|
||||
status: str # "added", "removed", "modified", "renamed"
|
||||
additions: int
|
||||
deletions: int
|
||||
changes: int
|
||||
raw_url: str | None = None
|
||||
contents_url: str | None = None
|
||||
patch: str | None = None
|
||||
previous_filename: str | None = None
|
||||
|
||||
class CreatePullRequestRequest(BaseModel):
|
||||
"""Request to create a pull request"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
title: str = Field(..., min_length=1, max_length=255, description="PR title")
|
||||
head: str = Field(..., description="Source branch")
|
||||
base: str = Field(..., description="Target branch")
|
||||
body: str | None = Field(None, description="PR description")
|
||||
assignee: str | None = Field(None, description="Username of assignee")
|
||||
assignees: list[str] | None = Field(None, description="List of assignee usernames")
|
||||
labels: list[int] | None = Field(None, description="List of label IDs")
|
||||
milestone: int | None = Field(None, description="Milestone ID")
|
||||
due_date: datetime | None = Field(None, description="Due date")
|
||||
|
||||
class UpdatePullRequestRequest(BaseModel):
|
||||
"""Request to update a pull request"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
title: str | None = Field(None, min_length=1, max_length=255, description="PR title")
|
||||
body: str | None = Field(None, description="PR description")
|
||||
assignee: str | None = Field(None, description="Username of assignee")
|
||||
assignees: list[str] | None = Field(None, description="List of assignee usernames")
|
||||
labels: list[int] | None = Field(None, description="List of label IDs")
|
||||
milestone: int | None = Field(None, description="Milestone ID")
|
||||
state: str | None = Field(None, description="State (open/closed)")
|
||||
due_date: datetime | None = Field(None, description="Due date")
|
||||
unset_due_date: bool | None = Field(None, description="Unset due date")
|
||||
allow_maintainer_edit: bool | None = Field(None, description="Allow maintainer edit")
|
||||
|
||||
@classmethod
|
||||
@field_validator("state")
|
||||
def validate_state(cls, v):
|
||||
if v and v not in ["open", "closed"]:
|
||||
raise ValueError("State must be 'open' or 'closed'")
|
||||
return v
|
||||
|
||||
class MergePullRequestRequest(BaseModel):
|
||||
"""Request to merge a pull request"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
Do: str = Field(
|
||||
..., description="Merge method (merge/rebase/rebase-merge/squash/manually-merged)"
|
||||
)
|
||||
MergeTitleField: str | None = Field(None, description="Merge commit title")
|
||||
MergeMessageField: str | None = Field(None, description="Merge commit message")
|
||||
delete_branch_after_merge: bool | None = Field(False, description="Delete branch after merge")
|
||||
force_merge: bool | None = Field(False, description="Force merge even if not mergeable")
|
||||
head_commit_id: str | None = Field(None, description="Expected HEAD commit SHA")
|
||||
merge_when_checks_succeed: bool | None = Field(False, description="Auto-merge when checks pass")
|
||||
|
||||
@classmethod
|
||||
@field_validator("Do")
|
||||
def validate_merge_method(cls, v):
|
||||
allowed = ["merge", "rebase", "rebase-merge", "squash", "manually-merged"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Merge method must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
class CreateReviewRequest(BaseModel):
|
||||
"""Request to create a review"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
body: str | None = Field(None, description="Review comment")
|
||||
event: str = Field(..., description="Review event (APPROVED/REQUEST_CHANGES/COMMENT)")
|
||||
comments: list[dict] | None = Field(None, description="Inline review comments")
|
||||
|
||||
@classmethod
|
||||
@field_validator("event")
|
||||
def validate_event(cls, v):
|
||||
allowed = ["APPROVED", "REQUEST_CHANGES", "COMMENT"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Review event must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
class RequestReviewersRequest(BaseModel):
|
||||
"""Request reviewers for a pull request"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
reviewers: list[str] = Field(..., min_items=1, description="List of reviewer usernames")
|
||||
team_reviewers: list[str] | None = Field(None, description="List of team slugs")
|
||||
|
||||
class PRListFilters(BaseModel):
|
||||
"""Filters for listing pull requests"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
state: str | None = Field("open", description="Filter by state (open/closed/all)")
|
||||
sort: str | None = Field(
|
||||
"created", description="Sort by (created/updated/comments/recentupdate)"
|
||||
)
|
||||
labels: str | None = Field(None, description="Comma-separated label IDs")
|
||||
milestone: str | None = Field(None, description="Milestone name")
|
||||
|
||||
@classmethod
|
||||
@field_validator("state")
|
||||
def validate_state(cls, v):
|
||||
if v and v not in ["open", "closed", "all"]:
|
||||
raise ValueError("State must be 'open', 'closed', or 'all'")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("sort")
|
||||
def validate_sort(cls, v):
|
||||
allowed = ["created", "updated", "comments", "recentupdate"]
|
||||
if v and v not in allowed:
|
||||
raise ValueError(f"Sort must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
196
plugins/gitea/schemas/repository.py
Normal file
196
plugins/gitea/schemas/repository.py
Normal file
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
Repository Pydantic Schemas
|
||||
|
||||
Validation schemas for Gitea repository operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from .common import GiteaPermissions, GiteaUser
|
||||
|
||||
class Repository(BaseModel):
|
||||
"""Gitea repository model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
owner: GiteaUser
|
||||
name: str
|
||||
full_name: str
|
||||
description: str | None = None
|
||||
empty: bool = False
|
||||
private: bool = False
|
||||
fork: bool = False
|
||||
template: bool = False
|
||||
parent: Optional["Repository"] = None
|
||||
mirror: bool = False
|
||||
size: int = 0
|
||||
language: str | None = None
|
||||
languages_url: str | None = None
|
||||
html_url: str
|
||||
ssh_url: str
|
||||
clone_url: str
|
||||
original_url: str | None = None
|
||||
website: str | None = None
|
||||
stars_count: int = 0
|
||||
forks_count: int = 0
|
||||
watchers_count: int = 0
|
||||
open_issues_count: int = 0
|
||||
open_pr_counter: int = 0
|
||||
release_counter: int = 0
|
||||
default_branch: str = "main"
|
||||
archived: bool = False
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
permissions: GiteaPermissions | None = None
|
||||
has_issues: bool = True
|
||||
internal_tracker: dict | None = None
|
||||
has_wiki: bool = False
|
||||
has_pull_requests: bool = True
|
||||
has_projects: bool = False
|
||||
ignore_whitespace_conflicts: bool = False
|
||||
allow_merge_commits: bool = True
|
||||
allow_rebase: bool = True
|
||||
allow_rebase_explicit: bool = True
|
||||
allow_squash_merge: bool = True
|
||||
default_merge_style: str = "merge"
|
||||
avatar_url: str | None = None
|
||||
|
||||
class Branch(BaseModel):
|
||||
"""Git branch model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
name: str
|
||||
commit: dict
|
||||
protected: bool = False
|
||||
required_approvals: int | None = None
|
||||
enable_status_check: bool = False
|
||||
status_check_contexts: list[str] | None = None
|
||||
user_can_push: bool = False
|
||||
user_can_merge: bool = False
|
||||
|
||||
class Tag(BaseModel):
|
||||
"""Git tag model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
name: str
|
||||
message: str | None = None
|
||||
id: str
|
||||
commit: dict
|
||||
zipball_url: str | None = None
|
||||
tarball_url: str | None = None
|
||||
|
||||
class FileContent(BaseModel):
|
||||
"""File content model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
name: str
|
||||
path: str
|
||||
sha: str
|
||||
size: int
|
||||
url: str
|
||||
html_url: str
|
||||
git_url: str
|
||||
download_url: str | None = None
|
||||
type: str # "file" or "dir"
|
||||
content: str | None = None # Base64 encoded
|
||||
encoding: str | None = None
|
||||
target: str | None = None # For symlinks
|
||||
submodule_git_url: str | None = None
|
||||
|
||||
class CreateRepositoryRequest(BaseModel):
|
||||
"""Request to create a repository"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=100, description="Repository name")
|
||||
description: str | None = Field(None, max_length=500, description="Repository description")
|
||||
private: bool = Field(default=False, description="Make repository private")
|
||||
auto_init: bool = Field(default=False, description="Initialize with README")
|
||||
gitignores: str | None = Field(None, description="Gitignore template")
|
||||
license: str | None = Field(None, description="License template")
|
||||
readme: str | None = Field(None, description="Readme template")
|
||||
default_branch: str | None = Field(None, description="Default branch name")
|
||||
trust_model: str | None = Field(
|
||||
None, description="Trust model (default, collaborator, committer, collaboratorcommitter)"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@field_validator("name")
|
||||
def validate_name(cls, v):
|
||||
"""Validate repository name"""
|
||||
if not v.replace("-", "").replace("_", "").replace(".", "").isalnum():
|
||||
raise ValueError(
|
||||
"Repository name can only contain alphanumeric characters, hyphens, underscores, and dots"
|
||||
)
|
||||
return v
|
||||
|
||||
class UpdateRepositoryRequest(BaseModel):
|
||||
"""Request to update a repository"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str | None = Field(None, min_length=1, max_length=100, description="New repository name")
|
||||
description: str | None = Field(None, max_length=500, description="Repository description")
|
||||
website: str | None = Field(None, description="Repository website")
|
||||
private: bool | None = Field(None, description="Make repository private")
|
||||
archived: bool | None = Field(None, description="Archive repository")
|
||||
has_issues: bool | None = Field(None, description="Enable issues")
|
||||
has_wiki: bool | None = Field(None, description="Enable wiki")
|
||||
default_branch: str | None = Field(None, description="Default branch")
|
||||
allow_merge_commits: bool | None = Field(None, description="Allow merge commits")
|
||||
allow_rebase: bool | None = Field(None, description="Allow rebase")
|
||||
allow_squash_merge: bool | None = Field(None, description="Allow squash merge")
|
||||
|
||||
class CreateBranchRequest(BaseModel):
|
||||
"""Request to create a branch"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
new_branch_name: str = Field(..., min_length=1, description="New branch name")
|
||||
old_branch_name: str | None = Field(None, description="Source branch (default: default branch)")
|
||||
old_ref_name: str | None = Field(None, description="Source commit SHA or ref")
|
||||
|
||||
class CreateTagRequest(BaseModel):
|
||||
"""Request to create a tag"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
tag_name: str = Field(..., min_length=1, description="Tag name")
|
||||
message: str | None = Field(None, description="Tag message")
|
||||
target: str | None = Field(None, description="Target commit SHA (default: latest)")
|
||||
|
||||
class CreateFileRequest(BaseModel):
|
||||
"""Request to create a file"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
content: str = Field(..., description="File content (will be Base64 encoded)")
|
||||
message: str = Field(..., min_length=1, description="Commit message")
|
||||
branch: str | None = Field(None, description="Branch name (default: default branch)")
|
||||
author_name: str | None = Field(None, description="Author name")
|
||||
author_email: str | None = Field(None, description="Author email")
|
||||
committer_name: str | None = Field(None, description="Committer name")
|
||||
committer_email: str | None = Field(None, description="Committer email")
|
||||
new_branch: str | None = Field(None, description="Create new branch for this commit")
|
||||
|
||||
class UpdateFileRequest(BaseModel):
|
||||
"""Request to update a file"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
content: str = Field(..., description="New file content (will be Base64 encoded)")
|
||||
message: str = Field(..., min_length=1, description="Commit message")
|
||||
sha: str = Field(..., description="SHA of the file being replaced")
|
||||
branch: str | None = Field(None, description="Branch name (default: default branch)")
|
||||
author_name: str | None = Field(None, description="Author name")
|
||||
author_email: str | None = Field(None, description="Author email")
|
||||
committer_name: str | None = Field(None, description="Committer name")
|
||||
committer_email: str | None = Field(None, description="Committer email")
|
||||
new_branch: str | None = Field(None, description="Create new branch for this commit")
|
||||
98
plugins/gitea/schemas/user.py
Normal file
98
plugins/gitea/schemas/user.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
User & Organization Pydantic Schemas
|
||||
|
||||
Validation schemas for Gitea user and organization operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
class User(BaseModel):
|
||||
"""Gitea user model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
login: str
|
||||
full_name: str | None = None
|
||||
email: str | None = None
|
||||
avatar_url: str
|
||||
language: str | None = None
|
||||
is_admin: bool = False
|
||||
last_login: datetime | None = None
|
||||
created: datetime
|
||||
restricted: bool = False
|
||||
active: bool = True
|
||||
prohibit_login: bool = False
|
||||
location: str | None = None
|
||||
website: str | None = None
|
||||
description: str | None = None
|
||||
visibility: str | None = None
|
||||
followers_count: int = 0
|
||||
following_count: int = 0
|
||||
starred_repos_count: int = 0
|
||||
username: str | None = None # Alias for login
|
||||
|
||||
class Organization(BaseModel):
|
||||
"""Gitea organization model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
name: str # org username
|
||||
full_name: str | None = None
|
||||
avatar_url: str
|
||||
description: str | None = None
|
||||
website: str | None = None
|
||||
location: str | None = None
|
||||
visibility: str # "public", "limited", "private"
|
||||
repo_admin_change_team_access: bool = False
|
||||
username: str | None = None # Alias for name
|
||||
|
||||
class Team(BaseModel):
|
||||
"""Organization team model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
name: str
|
||||
description: str | None = None
|
||||
organization: Organization | None = None
|
||||
permission: str # "none", "read", "write", "admin", "owner"
|
||||
can_create_org_repo: bool = False
|
||||
includes_all_repositories: bool = False
|
||||
units: list[str] | None = None
|
||||
units_map: dict | None = None
|
||||
|
||||
class TeamMember(BaseModel):
|
||||
"""Team member model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
user: User
|
||||
role: str | None = None # Role in team
|
||||
|
||||
class Email(BaseModel):
|
||||
"""User email model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
email: str
|
||||
verified: bool
|
||||
primary: bool
|
||||
|
||||
class SearchUsersRequest(BaseModel):
|
||||
"""Request to search users"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
q: str | None = Field(None, description="Search query")
|
||||
uid: int | None = Field(None, description="User ID")
|
||||
|
||||
class SearchOrgsRequest(BaseModel):
|
||||
"""Request to search organizations"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
q: str | None = Field(None, description="Search query")
|
||||
163
plugins/gitea/schemas/webhook.py
Normal file
163
plugins/gitea/schemas/webhook.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
Webhook Pydantic Schemas
|
||||
|
||||
Validation schemas for Gitea webhook operations.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
class Webhook(BaseModel):
|
||||
"""Gitea webhook model"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
type: str # "gitea", "gogs", "slack", "discord", etc.
|
||||
config: dict[str, Any]
|
||||
events: list[str]
|
||||
active: bool
|
||||
updated_at: datetime
|
||||
created_at: datetime
|
||||
|
||||
class WebhookConfig(BaseModel):
|
||||
"""Webhook configuration"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
url: str
|
||||
content_type: str = "json" # "json" or "form"
|
||||
secret: str | None = None
|
||||
http_method: str | None = "POST"
|
||||
branch_filter: str | None = None
|
||||
|
||||
class CreateWebhookRequest(BaseModel):
|
||||
"""Request to create a webhook"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
type: str = Field(
|
||||
default="gitea",
|
||||
description="Webhook type (gitea/gogs/slack/discord/dingtalk/telegram/msteams/feishu/wechatwork/packagist)",
|
||||
)
|
||||
config: dict[str, Any] = Field(..., description="Webhook configuration")
|
||||
events: list[str] = Field(..., min_items=1, description="List of events to trigger webhook")
|
||||
active: bool = Field(default=True, description="Activate webhook immediately")
|
||||
branch_filter: str | None = Field(None, description="Branch filter pattern")
|
||||
|
||||
@classmethod
|
||||
@field_validator("type")
|
||||
def validate_type(cls, v):
|
||||
allowed = [
|
||||
"gitea",
|
||||
"gogs",
|
||||
"slack",
|
||||
"discord",
|
||||
"dingtalk",
|
||||
"telegram",
|
||||
"msteams",
|
||||
"feishu",
|
||||
"wechatwork",
|
||||
"packagist",
|
||||
]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Webhook type must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("events")
|
||||
def validate_events(cls, v):
|
||||
allowed_events = [
|
||||
"create",
|
||||
"delete",
|
||||
"fork",
|
||||
"push",
|
||||
"issues",
|
||||
"issue_assign",
|
||||
"issue_label",
|
||||
"issue_milestone",
|
||||
"issue_comment",
|
||||
"pull_request",
|
||||
"pull_request_assign",
|
||||
"pull_request_label",
|
||||
"pull_request_milestone",
|
||||
"pull_request_comment",
|
||||
"pull_request_review_approved",
|
||||
"pull_request_review_rejected",
|
||||
"pull_request_review_comment",
|
||||
"pull_request_sync",
|
||||
"wiki",
|
||||
"repository",
|
||||
"release",
|
||||
]
|
||||
for event in v:
|
||||
if event not in allowed_events:
|
||||
raise ValueError(
|
||||
f"Invalid event: {event}. Must be one of: {', '.join(allowed_events)}"
|
||||
)
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("config")
|
||||
def validate_config(cls, v):
|
||||
"""Validate webhook config has required fields"""
|
||||
if "url" not in v:
|
||||
raise ValueError("Webhook config must contain 'url'")
|
||||
if "content_type" not in v:
|
||||
v["content_type"] = "json"
|
||||
return v
|
||||
|
||||
class UpdateWebhookRequest(BaseModel):
|
||||
"""Request to update a webhook"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
config: dict[str, Any] | None = Field(None, description="Webhook configuration")
|
||||
events: list[str] | None = Field(None, description="List of events to trigger webhook")
|
||||
active: bool | None = Field(None, description="Activate/deactivate webhook")
|
||||
branch_filter: str | None = Field(None, description="Branch filter pattern")
|
||||
|
||||
@classmethod
|
||||
@field_validator("events")
|
||||
def validate_events(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
allowed_events = [
|
||||
"create",
|
||||
"delete",
|
||||
"fork",
|
||||
"push",
|
||||
"issues",
|
||||
"issue_assign",
|
||||
"issue_label",
|
||||
"issue_milestone",
|
||||
"issue_comment",
|
||||
"pull_request",
|
||||
"pull_request_assign",
|
||||
"pull_request_label",
|
||||
"pull_request_milestone",
|
||||
"pull_request_comment",
|
||||
"pull_request_review_approved",
|
||||
"pull_request_review_rejected",
|
||||
"pull_request_review_comment",
|
||||
"pull_request_sync",
|
||||
"wiki",
|
||||
"repository",
|
||||
"release",
|
||||
]
|
||||
for event in v:
|
||||
if event not in allowed_events:
|
||||
raise ValueError(f"Invalid event: {event}")
|
||||
return v
|
||||
|
||||
class WebhookTestResult(BaseModel):
|
||||
"""Webhook test result"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
success: bool
|
||||
message: str | None = None
|
||||
response_code: int | None = None
|
||||
response_body: str | None = None
|
||||
Reference in New Issue
Block a user