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:
68
plugins/wordpress/schemas/__init__.py
Normal file
68
plugins/wordpress/schemas/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
WordPress Pydantic Schemas
|
||||
|
||||
Type-safe validation schemas for WordPress data structures.
|
||||
Part of Option B clean architecture refactoring.
|
||||
"""
|
||||
|
||||
from plugins.wordpress.schemas.common import (
|
||||
ErrorResponse,
|
||||
PaginationParams,
|
||||
StatusFilter,
|
||||
SuccessResponse,
|
||||
)
|
||||
from plugins.wordpress.schemas.media import MediaBase, MediaResponse, MediaUpdate, MediaUpload
|
||||
from plugins.wordpress.schemas.order import (
|
||||
OrderBase,
|
||||
OrderCreate,
|
||||
OrderLineItem,
|
||||
OrderResponse,
|
||||
OrderUpdate,
|
||||
)
|
||||
from plugins.wordpress.schemas.post import PostBase, PostCreate, PostResponse, PostUpdate
|
||||
from plugins.wordpress.schemas.product import (
|
||||
ProductBase,
|
||||
ProductCategory,
|
||||
ProductCreate,
|
||||
ProductResponse,
|
||||
ProductUpdate,
|
||||
ProductVariation,
|
||||
)
|
||||
from plugins.wordpress.schemas.seo import SEOData, SEOUpdate
|
||||
|
||||
# Note: Database, Bulk, and System schemas moved to wordpress_advanced plugin
|
||||
|
||||
__all__ = [
|
||||
# Common
|
||||
"PaginationParams",
|
||||
"StatusFilter",
|
||||
"ErrorResponse",
|
||||
"SuccessResponse",
|
||||
# Posts
|
||||
"PostBase",
|
||||
"PostCreate",
|
||||
"PostUpdate",
|
||||
"PostResponse",
|
||||
# Media
|
||||
"MediaBase",
|
||||
"MediaUpload",
|
||||
"MediaUpdate",
|
||||
"MediaResponse",
|
||||
# Products
|
||||
"ProductBase",
|
||||
"ProductCreate",
|
||||
"ProductUpdate",
|
||||
"ProductResponse",
|
||||
"ProductCategory",
|
||||
"ProductVariation",
|
||||
# Orders
|
||||
"OrderBase",
|
||||
"OrderCreate",
|
||||
"OrderUpdate",
|
||||
"OrderResponse",
|
||||
"OrderLineItem",
|
||||
# SEO
|
||||
"SEOData",
|
||||
"SEOUpdate",
|
||||
# Note: Database, Bulk, and System schemas moved to wordpress_advanced plugin
|
||||
]
|
||||
45
plugins/wordpress/schemas/common.py
Normal file
45
plugins/wordpress/schemas/common.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Common Pydantic Schemas
|
||||
|
||||
Shared validation schemas used across WordPress handlers.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
class PaginationParams(BaseModel):
|
||||
"""Pagination parameters for list endpoints"""
|
||||
|
||||
per_page: int = Field(default=10, ge=1, le=100, description="Number of items per page (1-100)")
|
||||
page: int = Field(default=1, ge=1, description="Page number (starts at 1)")
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
class StatusFilter(BaseModel):
|
||||
"""Status filter for posts/pages/products"""
|
||||
|
||||
status: str = Field(default="any", description="Filter by status")
|
||||
|
||||
@classmethod
|
||||
@field_validator("status")
|
||||
def validate_status(cls, v):
|
||||
allowed = ["publish", "draft", "pending", "private", "any", "future", "trash"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Status must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
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")
|
||||
56
plugins/wordpress/schemas/media.py
Normal file
56
plugins/wordpress/schemas/media.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Media Pydantic Schemas
|
||||
|
||||
Validation schemas for WordPress media library operations.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
|
||||
|
||||
class MediaBase(BaseModel):
|
||||
"""Base media schema"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
title: str | None = Field(None, description="Media title")
|
||||
alt_text: str | None = Field(None, description="Alternative text")
|
||||
caption: str | None = Field(None, description="Media caption")
|
||||
description: str | None = Field(None, description="Media description")
|
||||
|
||||
class MediaUpload(MediaBase):
|
||||
"""Schema for uploading media from URL"""
|
||||
|
||||
url: HttpUrl = Field(..., description="Source URL of the media file")
|
||||
filename: str | None = Field(None, description="Desired filename")
|
||||
|
||||
@classmethod
|
||||
@field_validator("filename")
|
||||
def validate_filename(cls, v):
|
||||
if v is not None:
|
||||
# Basic filename validation
|
||||
if "/" in v or "\\" in v:
|
||||
raise ValueError("Filename cannot contain path separators")
|
||||
if len(v) > 255:
|
||||
raise ValueError("Filename too long (max 255 characters)")
|
||||
return v
|
||||
|
||||
class MediaUpdate(MediaBase):
|
||||
"""Schema for updating media metadata"""
|
||||
|
||||
# All fields optional for updates
|
||||
pass
|
||||
|
||||
class MediaResponse(BaseModel):
|
||||
"""Schema for media response data"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
title: str
|
||||
alt_text: str
|
||||
caption: str
|
||||
description: str
|
||||
mime_type: str
|
||||
media_type: str
|
||||
source_url: str
|
||||
link: str
|
||||
date: str
|
||||
152
plugins/wordpress/schemas/order.py
Normal file
152
plugins/wordpress/schemas/order.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Order Pydantic Schemas
|
||||
|
||||
Validation schemas for WooCommerce orders and related entities.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
||||
|
||||
class OrderLineItem(BaseModel):
|
||||
"""Schema for order line item"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
product_id: int = Field(..., description="Product ID")
|
||||
quantity: int = Field(default=1, ge=1, description="Quantity")
|
||||
variation_id: int | None = Field(None, description="Variation ID")
|
||||
subtotal: str | None = Field(None, description="Line subtotal")
|
||||
total: str | None = Field(None, description="Line total")
|
||||
|
||||
class ShippingAddress(BaseModel):
|
||||
"""Schema for shipping address"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
first_name: str | None = Field(None, description="First name")
|
||||
last_name: str | None = Field(None, description="Last name")
|
||||
company: str | None = Field(None, description="Company name")
|
||||
address_1: str | None = Field(None, description="Address line 1")
|
||||
address_2: str | None = Field(None, description="Address line 2")
|
||||
city: str | None = Field(None, description="City")
|
||||
state: str | None = Field(None, description="State/Province")
|
||||
postcode: str | None = Field(None, description="Postal code")
|
||||
country: str | None = Field(None, description="Country code (ISO 3166-1 alpha-2)")
|
||||
|
||||
class BillingAddress(ShippingAddress):
|
||||
"""Schema for billing address (extends shipping)"""
|
||||
|
||||
email: EmailStr | None = Field(None, description="Email address")
|
||||
phone: str | None = Field(None, description="Phone number")
|
||||
|
||||
class OrderBase(BaseModel):
|
||||
"""Base order schema"""
|
||||
|
||||
status: str | None = Field("pending", description="Order status")
|
||||
customer_id: int | None = Field(None, description="Customer user ID")
|
||||
billing: BillingAddress | None = Field(None, description="Billing address")
|
||||
shipping: ShippingAddress | None = Field(None, description="Shipping address")
|
||||
payment_method: str | None = Field(None, description="Payment method ID")
|
||||
payment_method_title: str | None = Field(None, description="Payment method title")
|
||||
transaction_id: str | None = Field(None, description="Transaction ID")
|
||||
customer_note: str | None = Field(None, description="Customer note")
|
||||
line_items: list[OrderLineItem] | None = Field(None, description="Order line items")
|
||||
shipping_lines: list[dict[str, Any]] | None = Field(None, description="Shipping lines")
|
||||
fee_lines: list[dict[str, Any]] | None = Field(None, description="Fee lines")
|
||||
coupon_lines: list[dict[str, Any]] | None = Field(None, description="Coupon lines")
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
@classmethod
|
||||
@field_validator("status")
|
||||
def validate_status(cls, v):
|
||||
if v is not None:
|
||||
allowed = [
|
||||
"pending",
|
||||
"processing",
|
||||
"on-hold",
|
||||
"completed",
|
||||
"cancelled",
|
||||
"refunded",
|
||||
"failed",
|
||||
"trash",
|
||||
]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Status must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
class OrderCreate(OrderBase):
|
||||
"""Schema for creating a new order"""
|
||||
|
||||
line_items: list[OrderLineItem] = Field(
|
||||
..., min_length=1, description="Order line items (required)"
|
||||
)
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
class OrderUpdate(OrderBase):
|
||||
"""Schema for updating an existing order"""
|
||||
|
||||
# All fields optional for updates
|
||||
pass
|
||||
|
||||
class OrderStatusUpdate(BaseModel):
|
||||
"""Schema for updating order status"""
|
||||
|
||||
status: str = Field(..., description="New order status")
|
||||
|
||||
@classmethod
|
||||
@field_validator("status")
|
||||
def validate_status(cls, v):
|
||||
allowed = [
|
||||
"pending",
|
||||
"processing",
|
||||
"on-hold",
|
||||
"completed",
|
||||
"cancelled",
|
||||
"refunded",
|
||||
"failed",
|
||||
]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Status must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
class OrderResponse(BaseModel):
|
||||
"""Schema for order response data"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
number: str
|
||||
status: str
|
||||
total: str
|
||||
date_created: str
|
||||
billing: dict[str, Any]
|
||||
shipping: dict[str, Any]
|
||||
line_items: list[dict[str, Any]]
|
||||
|
||||
class CustomerBase(BaseModel):
|
||||
"""Base customer schema"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
email: EmailStr | None = Field(None, description="Customer email")
|
||||
first_name: str | None = Field(None, description="First name")
|
||||
last_name: str | None = Field(None, description="Last name")
|
||||
username: str | None = Field(None, description="Username")
|
||||
billing: BillingAddress | None = Field(None, description="Billing address")
|
||||
shipping: ShippingAddress | None = Field(None, description="Shipping address")
|
||||
|
||||
class CustomerCreate(CustomerBase):
|
||||
"""Schema for creating a new customer"""
|
||||
|
||||
email: EmailStr = Field(..., description="Customer email (required)")
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
class CustomerUpdate(CustomerBase):
|
||||
"""Schema for updating an existing customer"""
|
||||
|
||||
# All fields optional for updates
|
||||
pass
|
||||
102
plugins/wordpress/schemas/post.py
Normal file
102
plugins/wordpress/schemas/post.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
Post Pydantic Schemas
|
||||
|
||||
Validation schemas for WordPress posts, pages, and custom post types.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
class PostBase(BaseModel):
|
||||
"""Base post schema with common fields"""
|
||||
|
||||
title: str | None = Field(None, description="Post title")
|
||||
content: str | None = Field(None, description="Post content (HTML)")
|
||||
excerpt: str | None = Field(None, description="Post excerpt")
|
||||
status: str | None = Field("draft", description="Post status")
|
||||
slug: str | None = Field(None, description="Post slug (URL-friendly)")
|
||||
author: int | None = Field(None, description="Author user ID")
|
||||
featured_media: int | None = Field(None, description="Featured image media ID")
|
||||
comment_status: str | None = Field(None, description="Comment status (open/closed)")
|
||||
ping_status: str | None = Field(None, description="Ping status (open/closed)")
|
||||
format: str | None = Field(None, description="Post format")
|
||||
meta: dict | None = Field(None, description="Post meta fields")
|
||||
sticky: bool | None = Field(None, description="Sticky post flag")
|
||||
categories: list[int] | None = Field(None, description="Category IDs")
|
||||
tags: list[int] | None = Field(None, description="Tag IDs")
|
||||
|
||||
model_config = ConfigDict(extra="allow") # Allow additional fields for custom post types
|
||||
|
||||
@classmethod
|
||||
@field_validator("status")
|
||||
def validate_status(cls, v):
|
||||
if v is not None:
|
||||
allowed = ["publish", "draft", "pending", "private", "future"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Status must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("comment_status", "ping_status")
|
||||
def validate_comment_ping_status(cls, v):
|
||||
if v is not None:
|
||||
allowed = ["open", "closed"]
|
||||
if v not in allowed:
|
||||
raise ValueError("Status must be 'open' or 'closed'")
|
||||
return v
|
||||
|
||||
class PostCreate(PostBase):
|
||||
"""Schema for creating a new post"""
|
||||
|
||||
title: str = Field(..., min_length=1, description="Post title (required)")
|
||||
content: str = Field(..., description="Post content (required)")
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
class PostUpdate(PostBase):
|
||||
"""Schema for updating an existing post"""
|
||||
|
||||
# All fields optional for updates
|
||||
pass
|
||||
|
||||
class PostResponse(BaseModel):
|
||||
"""Schema for post response data"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
title: str
|
||||
content: str
|
||||
excerpt: str
|
||||
status: str
|
||||
slug: str
|
||||
link: str
|
||||
date: str
|
||||
modified: str
|
||||
author: int
|
||||
featured_media: int
|
||||
categories: list[int]
|
||||
tags: list[int]
|
||||
|
||||
class PageCreate(PostCreate):
|
||||
"""Schema for creating a new page"""
|
||||
|
||||
parent: int | None = Field(None, description="Parent page ID")
|
||||
menu_order: int | None = Field(None, description="Menu order")
|
||||
template: str | None = Field(None, description="Page template")
|
||||
|
||||
class PageUpdate(PostUpdate):
|
||||
"""Schema for updating an existing page"""
|
||||
|
||||
parent: int | None = Field(None, description="Parent page ID")
|
||||
menu_order: int | None = Field(None, description="Menu order")
|
||||
template: str | None = Field(None, description="Page template")
|
||||
|
||||
class CustomPostCreate(PostCreate):
|
||||
"""Schema for creating custom post types"""
|
||||
|
||||
post_type: str = Field(..., description="Custom post type")
|
||||
|
||||
class CustomPostUpdate(PostUpdate):
|
||||
"""Schema for updating custom post types"""
|
||||
|
||||
pass
|
||||
140
plugins/wordpress/schemas/product.py
Normal file
140
plugins/wordpress/schemas/product.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Product Pydantic Schemas
|
||||
|
||||
Validation schemas for WooCommerce products and related entities.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
class ProductBase(BaseModel):
|
||||
"""Base product schema"""
|
||||
|
||||
name: str | None = Field(None, description="Product name")
|
||||
type: str | None = Field("simple", description="Product type")
|
||||
status: str | None = Field("draft", description="Product status")
|
||||
featured: bool | None = Field(False, description="Featured product flag")
|
||||
catalog_visibility: str | None = Field("visible", description="Catalog visibility")
|
||||
description: str | None = Field(None, description="Product description (HTML)")
|
||||
short_description: str | None = Field(None, description="Short description (HTML)")
|
||||
sku: str | None = Field(None, description="Stock Keeping Unit")
|
||||
regular_price: str | None = Field(None, description="Regular price")
|
||||
sale_price: str | None = Field(None, description="Sale price")
|
||||
manage_stock: bool | None = Field(False, description="Manage stock flag")
|
||||
stock_quantity: int | None = Field(None, description="Stock quantity")
|
||||
stock_status: str | None = Field("instock", description="Stock status")
|
||||
weight: str | None = Field(None, description="Product weight")
|
||||
length: str | None = Field(None, description="Product length")
|
||||
width: str | None = Field(None, description="Product width")
|
||||
height: str | None = Field(None, description="Product height")
|
||||
categories: list[dict[str, Any]] | None = Field(None, description="Product categories")
|
||||
tags: list[dict[str, Any]] | None = Field(None, description="Product tags")
|
||||
images: list[dict[str, Any]] | None = Field(None, description="Product images")
|
||||
attributes: list[dict[str, Any]] | None = Field(None, description="Product attributes")
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
@classmethod
|
||||
@field_validator("type")
|
||||
def validate_type(cls, v):
|
||||
if v is not None:
|
||||
allowed = ["simple", "grouped", "external", "variable", "variation"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Type must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("status")
|
||||
def validate_status(cls, v):
|
||||
if v is not None:
|
||||
allowed = ["draft", "pending", "private", "publish"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Status must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("catalog_visibility")
|
||||
def validate_visibility(cls, v):
|
||||
if v is not None:
|
||||
allowed = ["visible", "catalog", "search", "hidden"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Visibility must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("stock_status")
|
||||
def validate_stock_status(cls, v):
|
||||
if v is not None:
|
||||
allowed = ["instock", "outofstock", "onbackorder"]
|
||||
if v not in allowed:
|
||||
raise ValueError(f"Stock status must be one of: {', '.join(allowed)}")
|
||||
return v
|
||||
|
||||
class ProductCreate(ProductBase):
|
||||
"""Schema for creating a new product"""
|
||||
|
||||
name: str = Field(..., min_length=1, description="Product name (required)")
|
||||
type: str = Field("simple", description="Product type")
|
||||
|
||||
class ProductUpdate(ProductBase):
|
||||
"""Schema for updating an existing product"""
|
||||
|
||||
# All fields optional for updates
|
||||
pass
|
||||
|
||||
class ProductResponse(BaseModel):
|
||||
"""Schema for product response data"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
id: int
|
||||
name: str
|
||||
slug: str
|
||||
type: str
|
||||
status: str
|
||||
featured: bool
|
||||
regular_price: str
|
||||
sale_price: str
|
||||
price: str
|
||||
stock_status: str
|
||||
permalink: str
|
||||
|
||||
class ProductCategory(BaseModel):
|
||||
"""Schema for product category"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
name: str = Field(..., description="Category name")
|
||||
slug: str | None = Field(None, description="Category slug")
|
||||
parent: int | None = Field(None, description="Parent category ID")
|
||||
description: str | None = Field(None, description="Category description")
|
||||
display: str | None = Field("default", description="Display type")
|
||||
image: dict[str, Any] | None = Field(None, description="Category image")
|
||||
|
||||
class ProductVariation(BaseModel):
|
||||
"""Schema for product variation"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
regular_price: str | None = Field(None, description="Regular price")
|
||||
sale_price: str | None = Field(None, description="Sale price")
|
||||
description: str | None = Field(None, description="Variation description")
|
||||
sku: str | None = Field(None, description="Stock Keeping Unit")
|
||||
manage_stock: bool | None = Field(False, description="Manage stock flag")
|
||||
stock_quantity: int | None = Field(None, description="Stock quantity")
|
||||
stock_status: str | None = Field("instock", description="Stock status")
|
||||
weight: str | None = Field(None, description="Variation weight")
|
||||
attributes: list[dict[str, Any]] | None = Field(None, description="Variation attributes")
|
||||
image: dict[str, Any] | None = Field(None, description="Variation image")
|
||||
|
||||
class ProductAttribute(BaseModel):
|
||||
"""Schema for product attribute"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
name: str = Field(..., description="Attribute name")
|
||||
slug: str | None = Field(None, description="Attribute slug")
|
||||
type: str | None = Field("select", description="Attribute type")
|
||||
order_by: str | None = Field("menu_order", description="Sort order")
|
||||
has_archives: bool | None = Field(False, description="Enable archives")
|
||||
80
plugins/wordpress/schemas/seo.py
Normal file
80
plugins/wordpress/schemas/seo.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
SEO Pydantic Schemas
|
||||
|
||||
Validation schemas for SEO plugin data (Yoast, RankMath, etc.).
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
class SEOData(BaseModel):
|
||||
"""Schema for SEO data (read)"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
title: str | None = Field(None, description="SEO title")
|
||||
description: str | None = Field(None, description="Meta description")
|
||||
keywords: str | None = Field(None, description="Focus keywords")
|
||||
canonical: str | None = Field(None, description="Canonical URL")
|
||||
og_title: str | None = Field(None, description="Open Graph title")
|
||||
og_description: str | None = Field(None, description="Open Graph description")
|
||||
og_image: str | None = Field(None, description="Open Graph image URL")
|
||||
twitter_title: str | None = Field(None, description="Twitter card title")
|
||||
twitter_description: str | None = Field(None, description="Twitter card description")
|
||||
twitter_image: str | None = Field(None, description="Twitter card image URL")
|
||||
robots: list[str] | None = Field(None, description="Robots meta tags")
|
||||
|
||||
class SEOUpdate(BaseModel):
|
||||
"""Schema for SEO data updates"""
|
||||
|
||||
title: str | None = Field(
|
||||
None, max_length=60, description="SEO title (max 60 chars recommended)"
|
||||
)
|
||||
description: str | None = Field(
|
||||
None, max_length=160, description="Meta description (max 160 chars recommended)"
|
||||
)
|
||||
keywords: str | None = Field(None, description="Focus keywords (comma-separated)")
|
||||
canonical: str | None = Field(None, description="Canonical URL")
|
||||
og_title: str | None = Field(None, description="Open Graph title")
|
||||
og_description: str | None = Field(None, description="Open Graph description")
|
||||
og_image: str | None = Field(None, description="Open Graph image URL")
|
||||
twitter_title: str | None = Field(None, description="Twitter card title")
|
||||
twitter_description: str | None = Field(None, description="Twitter card description")
|
||||
twitter_image: str | None = Field(None, description="Twitter card image URL")
|
||||
robots_index: bool | None = Field(None, description="Allow search engines to index")
|
||||
robots_follow: bool | None = Field(None, description="Allow search engines to follow links")
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
@classmethod
|
||||
@field_validator("title")
|
||||
def validate_title_length(cls, v):
|
||||
if v and len(v) > 70:
|
||||
# Warning, not error - allow but discourage
|
||||
pass
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
@field_validator("description")
|
||||
def validate_description_length(cls, v):
|
||||
if v and len(v) > 200:
|
||||
# Warning, not error - allow but discourage
|
||||
pass
|
||||
return v
|
||||
|
||||
class YoastSEO(SEOData):
|
||||
"""Yoast SEO specific data"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
yoast_wpseo_focuskw: str | None = Field(None, description="Yoast focus keyword")
|
||||
yoast_wpseo_metadesc: str | None = Field(None, description="Yoast meta description")
|
||||
yoast_wpseo_title: str | None = Field(None, description="Yoast SEO title")
|
||||
|
||||
class RankMathSEO(SEOData):
|
||||
"""RankMath SEO specific data"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
rank_math_focus_keyword: str | None = Field(None, description="RankMath focus keyword")
|
||||
rank_math_description: str | None = Field(None, description="RankMath meta description")
|
||||
rank_math_title: str | None = Field(None, description="RankMath SEO title")
|
||||
Reference in New Issue
Block a user