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:
airano
2026-02-17 08:34:44 +03:30
commit cf62e65c55
237 changed files with 87596 additions and 0 deletions

View 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",
]

View 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

View 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

View 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

View 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")

View 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")

View 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