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:
Yuzhiran Dev
2026-05-17 06:56:53 +08:00
parent 301dc3e438
commit 9c37c9a574
45 changed files with 3707 additions and 1366 deletions
+24 -12
View File
@@ -13,7 +13,7 @@ from ..core.generator import run_creator
from ..core.optimizer import run_optimizer
from ..core.sync import sync_all_topics
from ..core.scheduler import scheduler
from .auth import get_current_user
from .auth import get_current_user, org_filter
PROJECT_ROOT = Path(__file__).resolve().parents[4]
if os.getenv('PROJECT_ROOT'):
@@ -23,9 +23,9 @@ LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/system", tags=["system"])
def _aggregate_status_counts(db: Session):
def _aggregate_status_counts(q):
"""聚合状态计数,兼容中英文状态值"""
raw = db.query(Topic.status, func.count()).group_by(Topic.status).all()
raw = q.with_entities(Topic.status, func.count()).group_by(Topic.status).all()
mapping = {
'pending': ['pending', '待处理'],
'review': ['review', '待审查'],
@@ -43,7 +43,7 @@ def _aggregate_status_counts(db: Session):
@router.get("/status")
def get_status(db: Session = Depends(get_db)):
total = db.query(Topic).count()
counts = _aggregate_status_counts(db)
counts = _aggregate_status_counts(db.query(Topic))
today = date.today()
today_count = db.query(Topic).filter(func.date(Topic.created_at) == today).count()
return {
@@ -58,7 +58,7 @@ def get_status(db: Session = Depends(get_db)):
}
@router.post("/generate/run", dependencies=[Depends(get_current_user)])
def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Depends(get_db)):
def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Depends(get_db), current_user=Depends(get_current_user)):
logger.info(f"Received topic_id={topic_id}")
try:
result = run_creator(topic_id)
@@ -70,7 +70,7 @@ def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Dep
raise HTTPException(status_code=500, detail=str(e))
@router.post("/review/run", dependencies=[Depends(get_current_user)])
def trigger_review(topic_ids: List[str] = Body(None, embed=True), db: Session = Depends(get_db)):
def trigger_review(topic_ids: List[str] = Body(None, embed=True), db: Session = Depends(get_db), current_user=Depends(get_current_user)):
try:
result = run_optimizer(topic_ids)
if not result["ok"]:
@@ -86,7 +86,11 @@ def trigger_review(topic_ids: List[str] = Body(None, embed=True), db: Session =
if topic_ids:
updated = 0
for tid in topic_ids:
topic = db.query(Topic).filter(Topic.id == tid).first()
q = db.query(Topic).filter(Topic.id == tid)
of = org_filter(current_user, Topic)
if of is not True:
q = q.filter(of)
topic = q.first()
if topic and topic.status in ('review', '待审查'):
topic.status = 'ready'
if not topic.generated_at:
@@ -109,9 +113,13 @@ def get_logs(log_date: str, log_type: str = "creator"):
return {"log_date": log_date, "log_type": log_type, "content": lines}
@router.get("/pipeline/status", dependencies=[Depends(get_current_user)])
def get_pipeline_status(db: Session = Depends(get_db)):
total = db.query(Topic).count()
counts = _aggregate_status_counts(db)
def get_pipeline_status(db: Session = Depends(get_db), current_user=Depends(get_current_user)):
topic_base = db.query(Topic)
of = org_filter(current_user, Topic)
if of is not True:
topic_base = topic_base.filter(of)
total = topic_base.count()
counts = _aggregate_status_counts(topic_base)
log_files = {
"collector": LOGS_DIR / f"collector_{date.today().isoformat()}.log",
"creator": LOGS_DIR / f"creator_{date.today().isoformat()}.log",
@@ -135,9 +143,13 @@ def run_sync():
raise HTTPException(status_code=500, detail=str(e))
@router.get("/automation/topics")
def list_automation_topics(db: Session = Depends(get_db)):
def list_automation_topics(db: Session = Depends(get_db), current_user=Depends(get_current_user)):
try:
topics = db.query(Topic).order_by(Topic.created_at.desc()).limit(100).all()
topic_base = db.query(Topic)
of = org_filter(current_user, Topic)
if of is not True:
topic_base = topic_base.filter(of)
topics = topic_base.order_by(Topic.created_at.desc()).limit(100).all()
result = []
for t in topics:
result.append({