feat(models): add org_id to Article, PublishRecord, ContentMetrics, SearchRanking, GeoReadinessScore
- Add org_id column to 5 models with default='default' - Update to_dict() methods for ContentMetrics, SearchRanking, GeoReadinessScore - Add ALTER TABLE migrations in database.py init_db() - Add org_id to 6 Pydantic response schemas - Add require_role() helper factory in auth.py
This commit is contained in:
@@ -4,10 +4,10 @@ iteration: 1
|
||||
max_iterations: 500
|
||||
completion_promise: "DONE"
|
||||
initial_completion_promise: "DONE"
|
||||
started_at: "2026-06-16T23:59:41.756Z"
|
||||
started_at: "2026-06-17T06:11:18.800Z"
|
||||
session_id: "ses_1dede8a7affeG6DxtfPUOviXrp"
|
||||
ultrawork: true
|
||||
strategy: "continue"
|
||||
message_count_at_start: 8116
|
||||
message_count_at_start: 8133
|
||||
---
|
||||
开始全面改造,包括数据隔离与权限工作,改造实施并发数不要超过3
|
||||
全部按照你的建议执行启动
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_12cdfe924ffebqMk2WsK4OQD87",
|
||||
"updatedAt": "2026-06-17T01:09:35.927Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-06-17T01:09:35.927Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +160,16 @@ def get_current_admin(current_user: User = Depends(get_current_user)) -> User:
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="需要管理员权限")
|
||||
return current_user
|
||||
|
||||
def require_role(required_role: str):
|
||||
"""依赖项工厂:返回一个 FastAPI 依赖项,要求当前用户拥有指定角色。
|
||||
管理员自动拥有所有角色权限。"""
|
||||
def role_checker(current_user: User = Depends(get_current_user)):
|
||||
if current_user.role != required_role and current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail=f"需要{required_role}角色权限")
|
||||
return current_user
|
||||
return role_checker
|
||||
|
||||
@router.get("/me")
|
||||
def get_me(
|
||||
request: Request,
|
||||
|
||||
@@ -126,6 +126,11 @@ def init_db():
|
||||
("menus", "sort_order", "INTEGER DEFAULT 0"),
|
||||
("menus", "roles", "JSON DEFAULT '[]'::json"),
|
||||
("menus", "is_active", "BOOLEAN DEFAULT TRUE"),
|
||||
("publish_records", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("content_metrics", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("search_rankings", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("geo_readiness_scores", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
("articles", "org_id", "VARCHAR DEFAULT 'default'"),
|
||||
]:
|
||||
try:
|
||||
conn.execute(text(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col} {typ}"))
|
||||
|
||||
@@ -280,6 +280,7 @@ class Article(Base):
|
||||
|
||||
id = Column(String, primary_key=True)
|
||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=False)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
platform = Column(String, nullable=False)
|
||||
file_path = Column(String, nullable=False)
|
||||
title = Column(String, nullable=True)
|
||||
@@ -302,6 +303,7 @@ class PublishRecord(Base):
|
||||
platform = Column(String, nullable=False)
|
||||
action = Column(String, nullable=False)
|
||||
status = Column(String, nullable=False)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
operator = Column(String, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
suggestion = Column(Text, nullable=True)
|
||||
@@ -357,6 +359,7 @@ class ContentMetrics(Base):
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=False, index=True)
|
||||
platform = Column(String, nullable=False)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
publish_url = Column(String, nullable=True)
|
||||
views = Column(Integer, default=0)
|
||||
likes = Column(Integer, default=0)
|
||||
@@ -382,6 +385,7 @@ class ContentMetrics(Base):
|
||||
"id": self.id,
|
||||
"topic_id": self.topic_id,
|
||||
"platform": self.platform,
|
||||
"org_id": self.org_id,
|
||||
"publish_url": self.publish_url,
|
||||
"views": self.views,
|
||||
"likes": self.likes,
|
||||
@@ -869,6 +873,7 @@ class SearchRanking(Base):
|
||||
article_id = Column(String, ForeignKey("articles.id"), nullable=True)
|
||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
|
||||
keyword = Column(String, nullable=False, index=True)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
platform = Column(String, nullable=True)
|
||||
search_engine = Column(String, default="bing") # bing / baidu / google
|
||||
position = Column(Integer, nullable=True) # 搜索排名位置(null = 未上榜)
|
||||
@@ -888,6 +893,7 @@ class SearchRanking(Base):
|
||||
"article_id": self.article_id,
|
||||
"topic_id": self.topic_id,
|
||||
"keyword": self.keyword,
|
||||
"org_id": self.org_id,
|
||||
"platform": self.platform,
|
||||
"search_engine": self.search_engine,
|
||||
"position": self.position,
|
||||
@@ -911,6 +917,7 @@ class GeoReadinessScore(Base):
|
||||
article_id = Column(String, ForeignKey("articles.id"), nullable=True, index=True)
|
||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
|
||||
platform = Column(String, nullable=True)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
total_score = Column(Integer, default=0) # 总分 0-100
|
||||
has_schema = Column(Boolean, default=False) # 是否有结构化数据
|
||||
schema_types = Column(String, nullable=True) # 含有的 schema 类型列表
|
||||
@@ -928,6 +935,7 @@ class GeoReadinessScore(Base):
|
||||
"article_id": self.article_id,
|
||||
"topic_id": self.topic_id,
|
||||
"platform": self.platform,
|
||||
"org_id": self.org_id,
|
||||
"total_score": self.total_score,
|
||||
"has_schema": self.has_schema,
|
||||
"schema_types": self.schema_types,
|
||||
|
||||
@@ -151,6 +151,7 @@ class ArticleBase(BaseModel):
|
||||
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
|
||||
@@ -193,6 +194,7 @@ class ContentCalendarUpdate(BaseModel):
|
||||
|
||||
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
|
||||
@@ -230,6 +232,7 @@ class ContentMetricsUpdate(BaseModel):
|
||||
|
||||
class ContentMetricsResponse(ContentMetricsBase):
|
||||
id: int
|
||||
org_id: Optional[str] = None
|
||||
engagement_rate: float = 0
|
||||
last_fetched: Optional[datetime] = None
|
||||
created_at: Optional[datetime] = None
|
||||
@@ -265,6 +268,7 @@ class MediaAssetUpdate(BaseModel):
|
||||
|
||||
class MediaAssetResponse(MediaAssetBase):
|
||||
id: int
|
||||
org_id: Optional[str] = None
|
||||
usage_count: int = 0
|
||||
uploaded_by: Optional[str] = None
|
||||
created_at: Optional[datetime] = None
|
||||
@@ -340,6 +344,7 @@ class ContentTaskCreate(BaseModel):
|
||||
|
||||
class ContentTaskResponse(ContentTaskBase):
|
||||
id: int
|
||||
org_id: Optional[str] = None
|
||||
task_id: str
|
||||
topic_id: Optional[str] = None
|
||||
topic_title: Optional[str] = None
|
||||
@@ -454,6 +459,7 @@ class PublishRecordResponse(BaseModel):
|
||||
platform: str
|
||||
action: str
|
||||
status: str
|
||||
org_id: Optional[str] = None
|
||||
operator: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
suggestion: Optional[str] = None
|
||||
|
||||
Reference in New Issue
Block a user