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
|
max_iterations: 500
|
||||||
completion_promise: "DONE"
|
completion_promise: "DONE"
|
||||||
initial_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"
|
session_id: "ses_1dede8a7affeG6DxtfPUOviXrp"
|
||||||
ultrawork: true
|
ultrawork: true
|
||||||
strategy: "continue"
|
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":
|
if current_user.role != "admin":
|
||||||
raise HTTPException(status_code=403, detail="需要管理员权限")
|
raise HTTPException(status_code=403, detail="需要管理员权限")
|
||||||
return current_user
|
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")
|
@router.get("/me")
|
||||||
def get_me(
|
def get_me(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|||||||
@@ -126,6 +126,11 @@ def init_db():
|
|||||||
("menus", "sort_order", "INTEGER DEFAULT 0"),
|
("menus", "sort_order", "INTEGER DEFAULT 0"),
|
||||||
("menus", "roles", "JSON DEFAULT '[]'::json"),
|
("menus", "roles", "JSON DEFAULT '[]'::json"),
|
||||||
("menus", "is_active", "BOOLEAN DEFAULT TRUE"),
|
("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:
|
try:
|
||||||
conn.execute(text(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col} {typ}"))
|
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)
|
id = Column(String, primary_key=True)
|
||||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=False)
|
topic_id = Column(String, ForeignKey("topics.id"), nullable=False)
|
||||||
|
org_id = Column(String, default="default", nullable=True)
|
||||||
platform = Column(String, nullable=False)
|
platform = Column(String, nullable=False)
|
||||||
file_path = Column(String, nullable=False)
|
file_path = Column(String, nullable=False)
|
||||||
title = Column(String, nullable=True)
|
title = Column(String, nullable=True)
|
||||||
@@ -302,6 +303,7 @@ class PublishRecord(Base):
|
|||||||
platform = Column(String, nullable=False)
|
platform = Column(String, nullable=False)
|
||||||
action = Column(String, nullable=False)
|
action = Column(String, nullable=False)
|
||||||
status = Column(String, nullable=False)
|
status = Column(String, nullable=False)
|
||||||
|
org_id = Column(String, default="default", nullable=True)
|
||||||
operator = Column(String, nullable=True)
|
operator = Column(String, nullable=True)
|
||||||
description = Column(Text, nullable=True)
|
description = Column(Text, nullable=True)
|
||||||
suggestion = 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)
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=False, index=True)
|
topic_id = Column(String, ForeignKey("topics.id"), nullable=False, index=True)
|
||||||
platform = Column(String, nullable=False)
|
platform = Column(String, nullable=False)
|
||||||
|
org_id = Column(String, default="default", nullable=True)
|
||||||
publish_url = Column(String, nullable=True)
|
publish_url = Column(String, nullable=True)
|
||||||
views = Column(Integer, default=0)
|
views = Column(Integer, default=0)
|
||||||
likes = Column(Integer, default=0)
|
likes = Column(Integer, default=0)
|
||||||
@@ -382,6 +385,7 @@ class ContentMetrics(Base):
|
|||||||
"id": self.id,
|
"id": self.id,
|
||||||
"topic_id": self.topic_id,
|
"topic_id": self.topic_id,
|
||||||
"platform": self.platform,
|
"platform": self.platform,
|
||||||
|
"org_id": self.org_id,
|
||||||
"publish_url": self.publish_url,
|
"publish_url": self.publish_url,
|
||||||
"views": self.views,
|
"views": self.views,
|
||||||
"likes": self.likes,
|
"likes": self.likes,
|
||||||
@@ -869,6 +873,7 @@ class SearchRanking(Base):
|
|||||||
article_id = Column(String, ForeignKey("articles.id"), nullable=True)
|
article_id = Column(String, ForeignKey("articles.id"), nullable=True)
|
||||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
|
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
|
||||||
keyword = Column(String, nullable=False, index=True)
|
keyword = Column(String, nullable=False, index=True)
|
||||||
|
org_id = Column(String, default="default", nullable=True)
|
||||||
platform = Column(String, nullable=True)
|
platform = Column(String, nullable=True)
|
||||||
search_engine = Column(String, default="bing") # bing / baidu / google
|
search_engine = Column(String, default="bing") # bing / baidu / google
|
||||||
position = Column(Integer, nullable=True) # 搜索排名位置(null = 未上榜)
|
position = Column(Integer, nullable=True) # 搜索排名位置(null = 未上榜)
|
||||||
@@ -888,6 +893,7 @@ class SearchRanking(Base):
|
|||||||
"article_id": self.article_id,
|
"article_id": self.article_id,
|
||||||
"topic_id": self.topic_id,
|
"topic_id": self.topic_id,
|
||||||
"keyword": self.keyword,
|
"keyword": self.keyword,
|
||||||
|
"org_id": self.org_id,
|
||||||
"platform": self.platform,
|
"platform": self.platform,
|
||||||
"search_engine": self.search_engine,
|
"search_engine": self.search_engine,
|
||||||
"position": self.position,
|
"position": self.position,
|
||||||
@@ -911,6 +917,7 @@ class GeoReadinessScore(Base):
|
|||||||
article_id = Column(String, ForeignKey("articles.id"), nullable=True, index=True)
|
article_id = Column(String, ForeignKey("articles.id"), nullable=True, index=True)
|
||||||
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
|
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
|
||||||
platform = Column(String, nullable=True)
|
platform = Column(String, nullable=True)
|
||||||
|
org_id = Column(String, default="default", nullable=True)
|
||||||
total_score = Column(Integer, default=0) # 总分 0-100
|
total_score = Column(Integer, default=0) # 总分 0-100
|
||||||
has_schema = Column(Boolean, default=False) # 是否有结构化数据
|
has_schema = Column(Boolean, default=False) # 是否有结构化数据
|
||||||
schema_types = Column(String, nullable=True) # 含有的 schema 类型列表
|
schema_types = Column(String, nullable=True) # 含有的 schema 类型列表
|
||||||
@@ -928,6 +935,7 @@ class GeoReadinessScore(Base):
|
|||||||
"article_id": self.article_id,
|
"article_id": self.article_id,
|
||||||
"topic_id": self.topic_id,
|
"topic_id": self.topic_id,
|
||||||
"platform": self.platform,
|
"platform": self.platform,
|
||||||
|
"org_id": self.org_id,
|
||||||
"total_score": self.total_score,
|
"total_score": self.total_score,
|
||||||
"has_schema": self.has_schema,
|
"has_schema": self.has_schema,
|
||||||
"schema_types": self.schema_types,
|
"schema_types": self.schema_types,
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class ArticleBase(BaseModel):
|
|||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
content: Optional[str] = None
|
content: Optional[str] = None
|
||||||
status: str = "draft"
|
status: str = "draft"
|
||||||
|
org_id: Optional[str] = None
|
||||||
compliance_score: Optional[int] = None
|
compliance_score: Optional[int] = None
|
||||||
word_count: Optional[int] = None
|
word_count: Optional[int] = None
|
||||||
outline: Optional[str] = None
|
outline: Optional[str] = None
|
||||||
@@ -193,6 +194,7 @@ class ContentCalendarUpdate(BaseModel):
|
|||||||
|
|
||||||
class ContentCalendarResponse(ContentCalendarBase):
|
class ContentCalendarResponse(ContentCalendarBase):
|
||||||
id: int
|
id: int
|
||||||
|
org_id: Optional[str] = None
|
||||||
published_date: Optional[date] = None
|
published_date: Optional[date] = None
|
||||||
created_by: Optional[str] = None
|
created_by: Optional[str] = None
|
||||||
created_at: Optional[datetime] = None
|
created_at: Optional[datetime] = None
|
||||||
@@ -230,6 +232,7 @@ class ContentMetricsUpdate(BaseModel):
|
|||||||
|
|
||||||
class ContentMetricsResponse(ContentMetricsBase):
|
class ContentMetricsResponse(ContentMetricsBase):
|
||||||
id: int
|
id: int
|
||||||
|
org_id: Optional[str] = None
|
||||||
engagement_rate: float = 0
|
engagement_rate: float = 0
|
||||||
last_fetched: Optional[datetime] = None
|
last_fetched: Optional[datetime] = None
|
||||||
created_at: Optional[datetime] = None
|
created_at: Optional[datetime] = None
|
||||||
@@ -265,6 +268,7 @@ class MediaAssetUpdate(BaseModel):
|
|||||||
|
|
||||||
class MediaAssetResponse(MediaAssetBase):
|
class MediaAssetResponse(MediaAssetBase):
|
||||||
id: int
|
id: int
|
||||||
|
org_id: Optional[str] = None
|
||||||
usage_count: int = 0
|
usage_count: int = 0
|
||||||
uploaded_by: Optional[str] = None
|
uploaded_by: Optional[str] = None
|
||||||
created_at: Optional[datetime] = None
|
created_at: Optional[datetime] = None
|
||||||
@@ -340,6 +344,7 @@ class ContentTaskCreate(BaseModel):
|
|||||||
|
|
||||||
class ContentTaskResponse(ContentTaskBase):
|
class ContentTaskResponse(ContentTaskBase):
|
||||||
id: int
|
id: int
|
||||||
|
org_id: Optional[str] = None
|
||||||
task_id: str
|
task_id: str
|
||||||
topic_id: Optional[str] = None
|
topic_id: Optional[str] = None
|
||||||
topic_title: Optional[str] = None
|
topic_title: Optional[str] = None
|
||||||
@@ -454,6 +459,7 @@ class PublishRecordResponse(BaseModel):
|
|||||||
platform: str
|
platform: str
|
||||||
action: str
|
action: str
|
||||||
status: str
|
status: str
|
||||||
|
org_id: Optional[str] = None
|
||||||
operator: Optional[str] = None
|
operator: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
suggestion: Optional[str] = None
|
suggestion: Optional[str] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user