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:
@@ -947,3 +947,208 @@ class GeoReadinessScore(Base):
|
|||||||
"heading_structure_score": self.heading_structure_score,
|
"heading_structure_score": self.heading_structure_score,
|
||||||
"checked_at": self.checked_at.isoformat() if self.checked_at else None,
|
"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)
|
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