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:
@@ -1,19 +1,23 @@
|
||||
from fastapi import APIRouter, HTTPException, Query, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_
|
||||
from pathlib import Path
|
||||
import os
|
||||
from datetime import datetime, date
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import User, Article
|
||||
from .auth import get_current_user
|
||||
from ..models import User, Article, Topic
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/articles", tags=["articles"])
|
||||
|
||||
@router.get("/drafts")
|
||||
def list_drafts(topic_id: str = None, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
"""从 articles 表列出草稿"""
|
||||
query = db.query(Article)
|
||||
query = db.query(Article).join(Topic, Article.topic_id == Topic.id)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
query = query.filter(of)
|
||||
if topic_id:
|
||||
query = query.filter(Article.topic_id == topic_id)
|
||||
articles = query.order_by(Article.created_at.desc()).all()
|
||||
@@ -22,9 +26,114 @@ def list_drafts(topic_id: str = None, current_user: User = Depends(get_current_u
|
||||
result.setdefault(a.platform, []).append({"id": a.id, "topic_id": a.topic_id, "status": a.status})
|
||||
return {"articles": result}
|
||||
|
||||
@router.get("/list")
|
||||
def list_articles(
|
||||
platform: str = None,
|
||||
status: str = None,
|
||||
search: str = None,
|
||||
topic_id: str = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""列出所有文章(含选题标题)"""
|
||||
query = db.query(Article, Topic.title.label("topic_title")).join(Topic, Article.topic_id == Topic.id)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
query = query.filter(of)
|
||||
if platform:
|
||||
query = query.filter(Article.platform == platform)
|
||||
if status:
|
||||
query = query.filter(Article.status == status)
|
||||
if topic_id:
|
||||
query = query.filter(Article.topic_id == topic_id)
|
||||
if search:
|
||||
query = query.filter(
|
||||
or_(Article.id.ilike(f"%{search}%"), Topic.title.ilike(f"%{search}%"))
|
||||
)
|
||||
rows = query.order_by(Article.created_at.desc()).all()
|
||||
return {
|
||||
"articles": [
|
||||
{
|
||||
"id": r.Article.id,
|
||||
"topic_id": r.Article.topic_id,
|
||||
"topic_title": r.topic_title,
|
||||
"platform": r.Article.platform,
|
||||
"status": r.Article.status,
|
||||
"compliance_score": r.Article.compliance_score,
|
||||
"word_count": r.Article.word_count,
|
||||
"created_at": r.Article.created_at.isoformat() if r.Article.created_at else None,
|
||||
"images": r.Article.images or {},
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
}
|
||||
|
||||
@router.get("/detail/{article_id}")
|
||||
def get_article_detail(article_id: str, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
"""获取文章详情"""
|
||||
article = db.query(Article).filter(Article.id == article_id).first()
|
||||
if not article:
|
||||
raise HTTPException(status_code=404, detail="Article not found")
|
||||
topic = db.query(Topic).filter(Topic.id == article.topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="Article not found")
|
||||
return {
|
||||
"id": article.id,
|
||||
"topic_id": article.topic_id,
|
||||
"topic_title": topic.title if topic else None,
|
||||
"platform": article.platform,
|
||||
"file_path": article.file_path,
|
||||
"status": article.status,
|
||||
"compliance_score": article.compliance_score,
|
||||
"word_count": article.word_count,
|
||||
"outline": article.outline,
|
||||
"html_content": article.html_content,
|
||||
"images": article.images or {},
|
||||
"created_at": article.created_at.isoformat() if article.created_at else None,
|
||||
}
|
||||
|
||||
@router.delete("/{article_id}")
|
||||
def delete_article(article_id: str, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
"""删除文章"""
|
||||
article = db.query(Article).filter(Article.id == article_id).first()
|
||||
if not article:
|
||||
raise HTTPException(status_code=404, detail="Article not found")
|
||||
topic = db.query(Topic).filter(Topic.id == article.topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="Article not found")
|
||||
file_path = Path(article.file_path) if article.file_path else None
|
||||
db.delete(article)
|
||||
db.commit()
|
||||
if file_path and file_path.exists():
|
||||
try:
|
||||
file_path.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
return {"detail": "deleted"}
|
||||
|
||||
@router.get("/{topic_id}/images")
|
||||
def get_article_images(topic_id: str, platform: str = None, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
"""获取某选题在各平台的配图路径"""
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="Topic not found")
|
||||
query = db.query(Article).filter(Article.topic_id == topic_id)
|
||||
if platform:
|
||||
query = query.filter(Article.platform == platform)
|
||||
articles = query.all()
|
||||
return {
|
||||
topic_id: {
|
||||
a.platform: (a.images or {})
|
||||
for a in articles
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/{topic_id}/preview")
|
||||
def preview_article(topic_id: str, platform: str = "zhihu", current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
"""从 articles 表预览某选题的 HTML 内容"""
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="Topic not found")
|
||||
article_id = f"{platform}_{topic_id}"
|
||||
article = db.query(Article).filter(Article.id == article_id).first()
|
||||
if not article or not article.html_content:
|
||||
|
||||
Reference in New Issue
Block a user