Files
yu-zhi-ran/platform/backend/app/schemas.py
T
yuzhiran f1e6c11505 feat(models): add series column to Topic for content columns
Topic model: new series VARCHAR field (optional, indexed)
Schema: series in TopicBase/TopicCreate/TopicUpdate
API: GET /api/topics supports ?series= query param
DB migration: ALTER TABLE topics ADD COLUMN series

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-23 13:22:13 +08:00

802 lines
20 KiB
Python

from pydantic import BaseModel, ConfigDict, Field
from datetime import datetime, date
from typing import Optional, List, Dict, Any
class TopicFieldBase(BaseModel):
name: str
icon: Optional[str] = None
color: Optional[str] = None
description: Optional[str] = None
parent_id: Optional[int] = None
sort_order: int = 0
is_active: bool = True
class TopicFieldResponse(TopicFieldBase):
id: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class TopicConfigFieldBase(BaseModel):
name: str
key: str
field_type: str = "number"
weight: float = 1.0
options: List[Any] = []
min_value: Optional[float] = None
max_value: Optional[float] = None
is_required: bool = False
sort_order: int = 0
class TopicConfigFieldResponse(TopicConfigFieldBase):
id: int
field_id: int
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class TopicStatusConfigBase(BaseModel):
status: str
label: str
color: Optional[str] = None
icon: Optional[str] = None
sort_order: int = 0
is_default: bool = False
class TopicStatusConfigResponse(TopicStatusConfigBase):
id: int
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class TopicBase(BaseModel):
id: str
field_id: Optional[int] = None
field_name: Optional[str] = None
org_id: Optional[str] = None
title: str
format: Optional[str] = None
core_concept: Optional[str] = None
audience_pain: Optional[str] = None
unique_angle: Optional[str] = None
priority: Optional[str] = None
priority_score: int | float = 0
total_score: Optional[float] = None
status: str = "pending"
tags: List[str] = []
custom_data: Dict[str, Any] = {}
scoring_data: Dict[str, Any] = {}
lock_by: Optional[str] = None
lock_at: Optional[datetime] = None
series: Optional[str] = None
class TopicCreate(BaseModel):
id: Optional[str] = None
field_id: Optional[int] = None
title: str = Field(..., min_length=1)
format: Optional[str] = None
core_concept: Optional[str] = None
audience_pain: Optional[str] = None
unique_angle: Optional[str] = None
priority: Optional[str] = None
tags: List[str] = []
custom_data: Dict[str, Any] = {}
scoring_data: Dict[str, Any] = {}
series: Optional[str] = None
class TopicAnalyzeRequest(BaseModel):
raw_input: str = Field(..., min_length=1, description="用户输入的选题思路/内容描述")
reference_links: List[str] = Field(default=[], description="参考链接列表(可选)")
field_id: Optional[int] = Field(default=None, description="所属领域ID(可选)")
class TopicAnalyzeResponse(BaseModel):
title: str
format: str
core_concept: str
audience_pain: str
unique_angle: str
field_name: Optional[str] = None
tags: List[str] = []
priority: str = ""
class TopicUpdate(BaseModel):
field_id: Optional[int] = None
title: Optional[str] = None
format: Optional[str] = None
core_concept: Optional[str] = None
audience_pain: Optional[str] = None
unique_angle: Optional[str] = None
priority: Optional[str] = None
status: Optional[str] = None
tags: Optional[List[str]] = None
custom_data: Optional[Dict[str, Any]] = None
scoring_data: Optional[Dict[str, Any]] = None
priority_score: Optional[int] = None
total_score: Optional[float] = None
series: Optional[str] = None
class TopicResponse(TopicBase):
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
generated_at: Optional[datetime] = None
reviewed_at: Optional[datetime] = None
ready_at: Optional[date] = None
published_at: Optional[date] = None
compliance_score: Optional[int] = None
platform_urls: Dict[str, str] = {}
cases: List[Any] = []
model_config = ConfigDict(from_attributes=True)
class TopicScoreRequest(BaseModel):
scoring_data: Dict[str, Any]
class ArticleBase(BaseModel):
id: str
topic_id: str
platform: str
file_path: str
title: Optional[str] = None
content: Optional[str] = None
status: str = "draft"
org_id: Optional[str] = None
compliance_score: Optional[int] = None
word_count: Optional[int] = None
outline: Optional[str] = None
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class ArticleResponse(ArticleBase):
html_content: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class ContentCalendarBase(BaseModel):
topic_id: Optional[str] = None
field_id: Optional[int] = None
title: str
planned_date: date
platform: Optional[str] = None
status: str = "planned"
reminder_time: Optional[datetime] = None
notes: Optional[str] = None
class ContentCalendarCreate(ContentCalendarBase):
created_by: Optional[str] = None
class ContentCalendarUpdate(BaseModel):
topic_id: Optional[str] = None
title: Optional[str] = None
planned_date: Optional[date] = None
published_date: Optional[date] = None
platform: Optional[str] = None
status: Optional[str] = None
reminder_time: Optional[datetime] = None
notes: Optional[str] = None
class ContentCalendarResponse(ContentCalendarBase):
id: int
org_id: Optional[str] = None
published_date: Optional[date] = None
created_by: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
topic_status: Optional[str] = None
platform_icon: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class ContentMetricsBase(BaseModel):
topic_id: str
platform: str
publish_url: Optional[str] = None
views: int = 0
likes: int = 0
favorites: int = 0
comments: int = 0
shares: int = 0
class ContentMetricsCreate(ContentMetricsBase):
data_snapshot: Optional[Dict[str, Any]] = None
class ContentMetricsUpdate(BaseModel):
publish_url: Optional[str] = None
views: Optional[int] = None
likes: Optional[int] = None
favorites: Optional[int] = None
comments: Optional[int] = None
shares: Optional[int] = None
data_snapshot: Optional[Dict[str, Any]] = None
class ContentMetricsResponse(ContentMetricsBase):
id: int
org_id: Optional[str] = None
engagement_rate: float = 0
last_fetched: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class MediaAssetBase(BaseModel):
filename: str
file_path: str
file_url: Optional[str] = None
file_type: str
mime_type: Optional[str] = None
size: Optional[int] = None
width: Optional[int] = None
height: Optional[int] = None
thumbnail_path: Optional[str] = None
alt_text: Optional[str] = None
tags: List[str] = []
topic_ids: List[str] = []
class MediaAssetCreate(MediaAssetBase):
uploaded_by: Optional[str] = None
class MediaAssetUpdate(BaseModel):
alt_text: Optional[str] = None
tags: Optional[List[str]] = None
topic_ids: Optional[List[str]] = None
class MediaAssetResponse(MediaAssetBase):
id: int
org_id: Optional[str] = None
usage_count: int = 0
uploaded_by: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class PlatformConfigBase(BaseModel):
platform: str
name: str
icon: Optional[str] = None
website_url: Optional[str] = None
api_endpoint: Optional[str] = None
auth_config: Dict[str, Any] = {}
format_template: Dict[str, Any] = {}
compliance_rules: Dict[str, Any] = {}
default_format: Optional[str] = None
is_active: bool = True
requires_image: bool = False
image_count_min: int = 0
image_count_max: int = 0
image_width: int = 0
image_height: int = 0
min_words: int = 0
max_words: int = 0
class PlatformConfigCreate(PlatformConfigBase):
pass
class PlatformConfigUpdate(BaseModel):
name: Optional[str] = None
icon: Optional[str] = None
website_url: Optional[str] = None
api_endpoint: Optional[str] = None
auth_config: Optional[Dict[str, Any]] = None
format_template: Optional[Dict[str, Any]] = None
compliance_rules: Optional[Dict[str, Any]] = None
default_format: Optional[str] = None
is_active: Optional[bool] = None
requires_image: Optional[bool] = None
image_count_min: Optional[int] = None
image_count_max: Optional[int] = None
image_width: Optional[int] = None
image_height: Optional[int] = None
min_words: Optional[int] = None
max_words: Optional[int] = None
class PlatformConfigResponse(PlatformConfigBase):
id: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class ContentTaskBase(BaseModel):
topic_id: Optional[str] = None
stage: str
status: str = "pending"
progress: int = 0
message: Optional[str] = None
class ContentTaskCreate(BaseModel):
topic_id: Optional[str] = None
stage: str
created_by: Optional[str] = None
class ContentTaskResponse(ContentTaskBase):
id: int
org_id: Optional[str] = None
task_id: str
topic_id: Optional[str] = None
topic_title: Optional[str] = None
result_data: Dict[str, Any] = {}
error_msg: Optional[str] = None
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
duration: Optional[int] = None
created_by: Optional[str] = None
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class MetricsDashboard(BaseModel):
total_topics: int
topics_by_status: Dict[str, int]
total_published: int
total_views: int
total_likes: int
avg_engagement_rate: float
top_topics: List[Dict[str, Any]]
recent_metrics: List[ContentMetricsResponse]
class CalendarMonthResponse(BaseModel):
year: int
month: int
entries: List[ContentCalendarResponse]
stats: Dict[str, int]
class AuditLogBase(BaseModel):
user_id: Optional[int] = None
username: str
action: str
resource_type: Optional[str] = None
resource_id: Optional[str] = None
details: Optional[Dict[str, Any]] = None
ip_address: Optional[str] = None
user_agent: Optional[str] = None
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class AuditLogResponse(AuditLogBase):
id: int
class UserBase(BaseModel):
username: str
role: str = "user"
class UserCreate(UserBase):
password: str
org_id: Optional[str] = None
class UserUpdate(BaseModel):
username: Optional[str] = None
password: Optional[str] = None
role: Optional[str] = None
class UserResponse(UserBase):
id: int
org_id: Optional[str] = None
last_login: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class ChangePasswordRequest(BaseModel):
old_password: str
new_password: str
class LoginRequest(BaseModel):
username: str
password: str
class TokenResponse(BaseModel):
token: str
role: str
user: UserResponse
class PublishRequest(BaseModel):
topic_id: str
platform_urls: Dict[str, str]
class PublishActionRequest(BaseModel):
action: str
platform: Optional[str] = None
status: str
operator: Optional[str] = None
description: Optional[str] = None
suggestion: Optional[str] = None
url: Optional[str] = None
error_msg: Optional[str] = None
class PublishRecordResponse(BaseModel):
id: int
topic_id: str
platform: str
action: str
status: str
org_id: Optional[str] = None
operator: Optional[str] = None
description: Optional[str] = None
suggestion: Optional[str] = None
url: Optional[str] = None
error_msg: Optional[str] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class CaseBase(BaseModel):
title: str
field: str
summary: str
key_metrics: Optional[str] = None
date: Optional[str] = None
source: str
source_url: Optional[str] = None
credibility_rating: Optional[str] = None
china_applicability: Optional[str] = None
class CaseResponse(CaseBase):
id: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class TaskLogBase(BaseModel):
module_id: str
task_name: str
topic_id: Optional[str] = None
status: str
message: Optional[str] = None
error_trace: Optional[str] = None
triggered_by: str = "scheduler"
result_data: Dict[str, Any] = {}
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
duration: Optional[int] = None
next_run_time: Optional[datetime] = None
class TaskLogResponse(TaskLogBase):
id: int
model_config = ConfigDict(from_attributes=True)
class TaskConfigBase(BaseModel):
module_id: str
enabled: bool = True
params: Dict[str, Any] = {}
schedule: Optional[str] = None
class TaskConfigUpdate(BaseModel):
enabled: Optional[bool] = None
params: Optional[Dict[str, Any]] = None
schedule: Optional[str] = None
last_modified_by: Optional[str] = None
class TaskConfigResponse(TaskConfigBase):
id: int
last_modified_by: Optional[str] = None
last_run_at: Optional[datetime] = None
result_data: Optional[Dict[str, Any]] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class LLMConfigBase(BaseModel):
name: str
system_prompt: Optional[str] = None
user_prompt_template: str
temperature: float = 0.7
max_tokens: int = 131072
model: Optional[str] = None
provider: str = "opencode-go"
base_url: Optional[str] = None
api_key: Optional[str] = None
is_active: bool = True
is_default: bool = False
rate_limit: int = 0
rate_limit_window_minutes: int = 300
class LLMConfigResponse(LLMConfigBase):
id: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class SystemConfigBase(BaseModel):
key: str
value: Optional[str] = None
description: Optional[str] = None
class SystemConfigResponse(SystemConfigBase):
value: Optional[Any] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class SystemStatus(BaseModel):
total_topics: int
topics_by_status: Dict[str, int]
ready_topics: List[TopicResponse]
today_articles: int
compliance_rate: float
last_optimization: Optional[datetime] = None
execution_time: Optional[float] = None
class RoleBase(BaseModel):
name: str
description: str = ""
is_system: bool = False
class RoleCreate(RoleBase):
pass
class RoleUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
class RoleResponse(RoleBase):
id: int
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class MenuBase(BaseModel):
parent_id: Optional[int] = None
name: str
path: str
icon: str = ""
sort_order: int = 0
roles: List[str] = []
is_active: bool = True
class MenuCreate(MenuBase):
pass
class MenuUpdate(BaseModel):
parent_id: Optional[int] = None
name: Optional[str] = None
path: Optional[str] = None
icon: Optional[str] = None
sort_order: Optional[int] = None
roles: Optional[List[str]] = None
is_active: Optional[bool] = None
class MenuResponse(MenuBase):
id: int
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
# ========== 外部推广模块 ==========
class ExternalProductBase(BaseModel):
name: str
type: str = "website"
url: Optional[str] = None
domain: Optional[str] = None
app_id: Optional[str] = None
account_id: Optional[str] = None
description: Optional[str] = None
class ExternalProductCreate(ExternalProductBase):
pass
class ExternalProductUpdate(BaseModel):
name: Optional[str] = None
type: Optional[str] = None
url: Optional[str] = None
domain: Optional[str] = None
app_id: Optional[str] = None
account_id: Optional[str] = None
description: Optional[str] = None
class ExternalProductResponse(ExternalProductBase):
id: int
org_id: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class PromotionCampaignBase(BaseModel):
name: str
product_id: int
status: str = "active"
keywords: List[str] = []
target_engines: List[str] = []
notes: Optional[str] = None
class PromotionCampaignCreate(PromotionCampaignBase):
pass
class PromotionCampaignUpdate(BaseModel):
name: Optional[str] = None
status: Optional[str] = None
keywords: Optional[List[str]] = None
target_engines: Optional[List[str]] = None
notes: Optional[str] = None
class PromotionCampaignResponse(PromotionCampaignBase):
id: int
org_id: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class CampaignKeywordBase(BaseModel):
campaign_id: int
keyword: str
search_volume: Optional[int] = None
difficulty: Optional[float] = None
current_rank: Optional[int] = None
target_rank: Optional[int] = None
class CampaignKeywordCreate(CampaignKeywordBase):
pass
class CampaignKeywordUpdate(BaseModel):
search_volume: Optional[int] = None
difficulty: Optional[float] = None
current_rank: Optional[int] = None
target_rank: Optional[int] = None
class CampaignKeywordResponse(CampaignKeywordBase):
id: int
best_rank: Optional[int] = None
last_checked: Optional[datetime] = None
org_id: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class SEOAuditResponse(BaseModel):
id: int
product_id: int
audit_type: str
overall_score: Optional[float] = None
meta_score: Optional[float] = None
heading_score: Optional[float] = None
content_score: Optional[float] = None
perf_score: Optional[float] = None
links_score: Optional[float] = None
mobile_score: Optional[float] = None
page_count: Optional[int] = None
issues_found: Optional[int] = None
org_id: Optional[str] = None
checked_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class KeywordRankingResponse(BaseModel):
id: int
campaign_id: Optional[int] = None
keyword_id: Optional[int] = None
product_id: Optional[int] = None
keyword: str
search_engine: str
rank: Optional[int] = None
url_found: Optional[str] = None
org_id: Optional[str] = None
checked_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class OptimizationTaskBase(BaseModel):
category: str
severity: str = "medium"
issue: str
recommendation: Optional[str] = None
class OptimizationTaskCreate(OptimizationTaskBase):
audit_id: int
product_id: int
class OptimizationTaskUpdate(BaseModel):
status: Optional[str] = None
severity: Optional[str] = None
recommendation: Optional[str] = None
class OptimizationTaskResponse(OptimizationTaskBase):
id: int
audit_id: int
product_id: int
status: str
org_id: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)