feat(models): add external promotion tables (6 models)
Add ExternalProduct, PromotionCampaign, CampaignKeyword, SEOAudit, KeywordRanking, OptimizationTask models and 18 Pydantic schemas for the external promotion module. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -946,4 +946,209 @@ class GeoReadinessScore(Base):
|
||||
"readability_score": self.readability_score,
|
||||
"heading_structure_score": self.heading_structure_score,
|
||||
"checked_at": self.checked_at.isoformat() if self.checked_at else None,
|
||||
}
|
||||
|
||||
|
||||
class ExternalProduct(Base):
|
||||
"""外部推广产品"""
|
||||
__tablename__ = "external_products"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
name = Column(String, nullable=False, comment="产品名称")
|
||||
type = Column(String, nullable=False, comment="类型: website/article/wechat_account/miniprogram")
|
||||
url = Column(String, nullable=True, comment="主URL")
|
||||
domain = Column(String, nullable=True, comment="域名")
|
||||
app_id = Column(String, nullable=True, comment="小程序AppID")
|
||||
account_id = Column(String, nullable=True, comment="公众号ID")
|
||||
description = Column(Text, nullable=True, comment="产品描述")
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"type": self.type,
|
||||
"url": self.url,
|
||||
"domain": self.domain,
|
||||
"app_id": self.app_id,
|
||||
"account_id": self.account_id,
|
||||
"description": self.description,
|
||||
"org_id": self.org_id,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
class PromotionCampaign(Base):
|
||||
"""推广活动"""
|
||||
__tablename__ = "promotion_campaigns"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
name = Column(String, nullable=False, comment="活动名称")
|
||||
product_id = Column(Integer, ForeignKey("external_products.id"), nullable=False)
|
||||
status = Column(String, default="active", comment="状态: active/paused/completed")
|
||||
keywords = Column(JSON, default=list, comment="目标关键词列表")
|
||||
target_engines = Column(JSON, default=list, comment="目标搜索引擎列表")
|
||||
notes = Column(Text, nullable=True, comment="备注")
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
product = relationship("ExternalProduct", backref="campaigns")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"product_id": self.product_id,
|
||||
"status": self.status,
|
||||
"keywords": self.keywords or [],
|
||||
"target_engines": self.target_engines or [],
|
||||
"notes": self.notes,
|
||||
"org_id": self.org_id,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
class CampaignKeyword(Base):
|
||||
"""推广关键词"""
|
||||
__tablename__ = "campaign_keywords"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
campaign_id = Column(Integer, ForeignKey("promotion_campaigns.id"), nullable=False)
|
||||
keyword = Column(String, nullable=False, comment="关键词")
|
||||
search_volume = Column(Integer, nullable=True, comment="搜索量")
|
||||
difficulty = Column(Float, nullable=True, comment="竞争难度 0-1")
|
||||
current_rank = Column(Integer, nullable=True, comment="当前排名")
|
||||
target_rank = Column(Integer, nullable=True, comment="目标排名")
|
||||
best_rank = Column(Integer, nullable=True, comment="历史最佳排名")
|
||||
last_checked = Column(DateTime(timezone=True), nullable=True, comment="最后检查时间")
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
campaign = relationship("PromotionCampaign", backref="campaign_keywords")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"campaign_id": self.campaign_id,
|
||||
"keyword": self.keyword,
|
||||
"search_volume": self.search_volume,
|
||||
"difficulty": self.difficulty,
|
||||
"current_rank": self.current_rank,
|
||||
"target_rank": self.target_rank,
|
||||
"best_rank": self.best_rank,
|
||||
"last_checked": self.last_checked.isoformat() if self.last_checked else None,
|
||||
"org_id": self.org_id,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
class SEOAudit(Base):
|
||||
"""SEO审计报告"""
|
||||
__tablename__ = "seo_audits"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
product_id = Column(Integer, ForeignKey("external_products.id"), nullable=False)
|
||||
audit_type = Column(String, default="full", comment="审计类型: full/quick")
|
||||
overall_score = Column(Float, nullable=True, comment="总分 0-100")
|
||||
meta_score = Column(Float, nullable=True, comment="Meta标签评分")
|
||||
heading_score = Column(Float, nullable=True, comment="标题结构评分")
|
||||
content_score = Column(Float, nullable=True, comment="内容评分")
|
||||
perf_score = Column(Float, nullable=True, comment="性能评分")
|
||||
links_score = Column(Float, nullable=True, comment="链接评分")
|
||||
mobile_score = Column(Float, nullable=True, comment="移动端评分")
|
||||
raw_data = Column(JSON, default=dict, comment="审计详情JSON")
|
||||
page_count = Column(Integer, nullable=True, comment="审计页面数")
|
||||
issues_found = Column(Integer, nullable=True, comment="发现问题数")
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
checked_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
product = relationship("ExternalProduct", backref="audits")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"product_id": self.product_id,
|
||||
"audit_type": self.audit_type,
|
||||
"overall_score": self.overall_score,
|
||||
"meta_score": self.meta_score,
|
||||
"heading_score": self.heading_score,
|
||||
"content_score": self.content_score,
|
||||
"perf_score": self.perf_score,
|
||||
"links_score": self.links_score,
|
||||
"mobile_score": self.mobile_score,
|
||||
"page_count": self.page_count,
|
||||
"issues_found": self.issues_found,
|
||||
"org_id": self.org_id,
|
||||
"checked_at": self.checked_at.isoformat() if self.checked_at else None,
|
||||
}
|
||||
|
||||
|
||||
class KeywordRanking(Base):
|
||||
"""外部关键词多引擎排名"""
|
||||
__tablename__ = "keyword_rankings"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
campaign_id = Column(Integer, ForeignKey("promotion_campaigns.id"), nullable=True)
|
||||
keyword_id = Column(Integer, ForeignKey("campaign_keywords.id"), nullable=True)
|
||||
product_id = Column(Integer, ForeignKey("external_products.id"), nullable=True)
|
||||
keyword = Column(String, nullable=False, comment="关键词")
|
||||
search_engine = Column(String, nullable=False, comment="搜索引擎: baidu/360/sogou/wechat/bing/google")
|
||||
rank = Column(Integer, nullable=True, comment="排名位置")
|
||||
url_found = Column(String, nullable=True, comment="排名URL")
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
checked_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"campaign_id": self.campaign_id,
|
||||
"keyword_id": self.keyword_id,
|
||||
"product_id": self.product_id,
|
||||
"keyword": self.keyword,
|
||||
"search_engine": self.search_engine,
|
||||
"rank": self.rank,
|
||||
"url_found": self.url_found,
|
||||
"org_id": self.org_id,
|
||||
"checked_at": self.checked_at.isoformat() if self.checked_at else None,
|
||||
}
|
||||
|
||||
|
||||
class OptimizationTask(Base):
|
||||
"""优化建议任务"""
|
||||
__tablename__ = "optimization_tasks"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
audit_id = Column(Integer, ForeignKey("seo_audits.id"), nullable=False)
|
||||
product_id = Column(Integer, ForeignKey("external_products.id"), nullable=False)
|
||||
category = Column(String, nullable=False, comment="分类: meta/heading/content/performance/links/mobile/other")
|
||||
severity = Column(String, default="medium", comment="严重度: high/medium/low")
|
||||
issue = Column(Text, nullable=False, comment="问题描述")
|
||||
recommendation = Column(Text, nullable=True, comment="优化建议")
|
||||
status = Column(String, default="open", comment="状态: open/resolved/ignored")
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
audit = relationship("SEOAudit", backref="optimization_tasks")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"audit_id": self.audit_id,
|
||||
"product_id": self.product_id,
|
||||
"category": self.category,
|
||||
"severity": self.severity,
|
||||
"issue": self.issue,
|
||||
"recommendation": self.recommendation,
|
||||
"status": self.status,
|
||||
"org_id": self.org_id,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
@@ -636,3 +636,163 @@ class MenuResponse(MenuBase):
|
||||
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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user