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:
@@ -10,11 +10,27 @@ from ..schemas import (
|
||||
ContentCalendarCreate, ContentCalendarUpdate, ContentCalendarResponse,
|
||||
ContentCalendarBase
|
||||
)
|
||||
from .auth import get_current_user
|
||||
from .auth import get_current_user, org_filter
|
||||
|
||||
router = APIRouter(prefix="/api/calendar", tags=["calendar"])
|
||||
|
||||
|
||||
PLATFORM_ICONS = {
|
||||
"zhihu": "知",
|
||||
"wechat": "微",
|
||||
"xiaohongshu": "红"
|
||||
}
|
||||
|
||||
def enrich_entry(entry, db):
|
||||
d = entry.to_dict()
|
||||
d["platform_icon"] = PLATFORM_ICONS.get(entry.platform, "📝")
|
||||
d["topic_status"] = None
|
||||
if entry.topic_id:
|
||||
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
|
||||
if topic:
|
||||
d["topic_status"] = topic.status
|
||||
return d
|
||||
|
||||
@router.get("", response_model=List[ContentCalendarResponse])
|
||||
def get_calendar(
|
||||
year: Optional[int] = Query(None),
|
||||
@@ -28,10 +44,17 @@ def get_calendar(
|
||||
start = date(year, month, 1)
|
||||
last_day = monthrange(year, month)[1]
|
||||
end = date(year, month, last_day)
|
||||
return db.query(ContentCalendar).filter(
|
||||
base = db.query(ContentCalendar).filter(
|
||||
ContentCalendar.planned_date >= start,
|
||||
ContentCalendar.planned_date <= end
|
||||
).order_by(ContentCalendar.planned_date).all()
|
||||
)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
base = base.outerjoin(Topic, ContentCalendar.topic_id == Topic.id).filter(
|
||||
(ContentCalendar.topic_id.is_(None)) | (Topic.org_id == current_user.org_id)
|
||||
)
|
||||
entries = base.order_by(ContentCalendar.planned_date).all()
|
||||
return [enrich_entry(e, db) for e in entries]
|
||||
|
||||
|
||||
@router.get("/entries", response_model=List[ContentCalendarResponse])
|
||||
@@ -45,6 +68,11 @@ def list_entries(
|
||||
current_user=Depends(get_current_user)
|
||||
):
|
||||
query = db.query(ContentCalendar)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
query = query.outerjoin(Topic, ContentCalendar.topic_id == Topic.id).filter(
|
||||
(ContentCalendar.topic_id.is_(None)) | (Topic.org_id == current_user.org_id)
|
||||
)
|
||||
if start_date:
|
||||
query = query.filter(ContentCalendar.planned_date >= start_date)
|
||||
if end_date:
|
||||
@@ -66,6 +94,8 @@ def create_entry(
|
||||
topic = db.query(Topic).filter(Topic.id == data.topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
if current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
entry = ContentCalendar(**data.model_dump())
|
||||
db.add(entry)
|
||||
@@ -135,6 +165,8 @@ def bind_topic_to_entry(
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
if current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
entry.topic_id = topic_id
|
||||
entry.title = topic.title
|
||||
if topic.field_name:
|
||||
@@ -154,10 +186,16 @@ def calendar_stats(
|
||||
start = date(year, month, 1)
|
||||
last_day = monthrange(year, month)[1]
|
||||
end = date(year, month, last_day)
|
||||
entries = db.query(ContentCalendar).filter(
|
||||
q = db.query(ContentCalendar).filter(
|
||||
ContentCalendar.planned_date >= start,
|
||||
ContentCalendar.planned_date <= end
|
||||
).all()
|
||||
)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
q = q.outerjoin(Topic, ContentCalendar.topic_id == Topic.id).filter(
|
||||
(ContentCalendar.topic_id.is_(None)) | (Topic.org_id == current_user.org_id)
|
||||
)
|
||||
entries = q.all()
|
||||
|
||||
stats = {"total": len(entries), "planned": 0, "published": 0, "delayed": 0, "cancelled": 0}
|
||||
for e in entries:
|
||||
@@ -177,6 +215,8 @@ def create_from_topic(
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
if current_user.role != "admin" and topic.org_id != current_user.org_id:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
entry = ContentCalendar(
|
||||
topic_id=topic_id,
|
||||
field_id=topic.field_id,
|
||||
|
||||
Reference in New Issue
Block a user