feat: Phase 4 多租户隔离 + 四阶段升级测试 + CSS 统一化
Phase 4: org_id 注入 JWT/API 过滤/组织管理 CRUD/前端组织列 测试: tests/test_phase_upgrades.py 97项全覆盖 CSS: theme-modern.css 共享 mobile-card-list/status-dot/search-bar 等模式 修复: initial_data.py LLM配置 NOT NULL 约束, TopicResponse 含 org_id
This commit is contained in:
@@ -41,6 +41,7 @@ class User(Base):
|
||||
username = Column(String, unique=True, nullable=False, index=True)
|
||||
password_hash = Column(String, nullable=False)
|
||||
role = Column(String, default="user", nullable=False)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
last_login = Column(DateTime(timezone=True), nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
@@ -50,6 +51,7 @@ class User(Base):
|
||||
"id": self.id,
|
||||
"username": self.username,
|
||||
"role": self.role,
|
||||
"org_id": self.org_id,
|
||||
"last_login": self.last_login.isoformat() if self.last_login else None,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
@@ -153,6 +155,7 @@ class Topic(Base):
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
field_id = Column(Integer, ForeignKey("topic_fields.id"), nullable=True)
|
||||
field_name = Column(String, nullable=True)
|
||||
org_id = Column(String, default="default", nullable=True)
|
||||
title = Column(String, nullable=False)
|
||||
format = Column(String)
|
||||
core_concept = Column(Text)
|
||||
@@ -199,6 +202,7 @@ class Article(Base):
|
||||
html_content = Column(Text)
|
||||
word_count = Column(Integer, nullable=True)
|
||||
outline = Column(Text, nullable=True)
|
||||
images = Column(JSON, default=dict) # {"cover": "/path/to/cover.png", "chart": "/path/to/chart.png"}
|
||||
|
||||
|
||||
class PublishRecord(Base):
|
||||
@@ -482,6 +486,9 @@ class LLMConfig(Base):
|
||||
temperature = Column(Float, default=0.7)
|
||||
max_tokens = Column(Integer, default=2000)
|
||||
model = Column(String, nullable=True)
|
||||
provider = Column(String, default="opencode-go") # opencode-go / nvidia
|
||||
base_url = Column(String, nullable=True)
|
||||
api_key = Column(String, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
@@ -495,6 +502,9 @@ class LLMConfig(Base):
|
||||
"temperature": self.temperature,
|
||||
"max_tokens": self.max_tokens,
|
||||
"model": self.model,
|
||||
"provider": self.provider,
|
||||
"base_url": self.base_url,
|
||||
"api_key": f"{self.api_key[:8]}..." if self.api_key else None,
|
||||
"is_active": self.is_active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
@@ -520,4 +530,68 @@ class SystemConfig(Base):
|
||||
"description": self.description,
|
||||
"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 CollectorCategory(Base):
|
||||
"""采集类别(可在运营管理中动态编辑)"""
|
||||
__tablename__ = "collector_categories"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
name = Column(String, unique=True, nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
search_query = Column(String, nullable=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
sources = relationship("CollectorSource", back_populates="category", cascade="all, delete-orphan")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"search_query": self.search_query,
|
||||
"sort_order": self.sort_order,
|
||||
"is_active": self.is_active,
|
||||
"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 CollectorSource(Base):
|
||||
"""采集信息源(可在运营管理中动态编辑)"""
|
||||
__tablename__ = "collector_sources"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
category_id = Column(Integer, ForeignKey("collector_categories.id"), nullable=True)
|
||||
name = Column(String, nullable=False)
|
||||
source_type = Column(String, nullable=False) # rss / web_search / local
|
||||
url = Column(Text, nullable=True)
|
||||
query = Column(String, nullable=True)
|
||||
credibility = Column(String, default="medium")
|
||||
focus = Column(String, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
category = relationship("CollectorCategory", back_populates="sources")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"category_id": self.category_id,
|
||||
"name": self.name,
|
||||
"source_type": self.source_type,
|
||||
"url": self.url,
|
||||
"query": self.query,
|
||||
"credibility": self.credibility,
|
||||
"focus": self.focus,
|
||||
"is_active": self.is_active,
|
||||
"sort_order": self.sort_order,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
Reference in New Issue
Block a user