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:
Yuzhiran Dev
2026-06-17 14:27:27 +08:00
parent 00431c827b
commit dcfeccc97b
6 changed files with 42 additions and 3 deletions
+3 -3
View File
@@ -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"
}
}
}
+10
View File
@@ -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,
+5
View File
@@ -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}"))
+8
View File
@@ -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,
+6
View File
@@ -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