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:
@@ -0,0 +1,127 @@
|
||||
"""采集管理 API:类别与信息源的 CRUD"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import CollectorCategory, CollectorSource
|
||||
from .auth import get_current_admin
|
||||
|
||||
router = APIRouter(prefix="/api/admin/collector", tags=["collector_mgmt"], dependencies=[Depends(get_current_admin)])
|
||||
|
||||
# ---------- Schemas ----------
|
||||
class CategoryCreate(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
search_query: Optional[str] = None
|
||||
sort_order: int = 0
|
||||
is_active: bool = True
|
||||
|
||||
class CategoryUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
search_query: Optional[str] = None
|
||||
sort_order: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
class SourceCreate(BaseModel):
|
||||
category_id: Optional[int] = None
|
||||
name: str
|
||||
source_type: str
|
||||
url: Optional[str] = None
|
||||
query: Optional[str] = None
|
||||
credibility: str = "medium"
|
||||
focus: Optional[str] = None
|
||||
is_active: bool = True
|
||||
sort_order: int = 0
|
||||
|
||||
class SourceUpdate(BaseModel):
|
||||
category_id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
source_type: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
query: Optional[str] = None
|
||||
credibility: Optional[str] = None
|
||||
focus: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
sort_order: Optional[int] = None
|
||||
|
||||
# ---------- Categories ----------
|
||||
@router.get("/categories")
|
||||
def list_categories(db: Session = Depends(get_db)):
|
||||
cats = db.query(CollectorCategory).order_by(CollectorCategory.sort_order).all()
|
||||
result = []
|
||||
for c in cats:
|
||||
d = c.to_dict()
|
||||
d["source_count"] = db.query(CollectorSource).filter(CollectorSource.category_id == c.id).count()
|
||||
result.append(d)
|
||||
return result
|
||||
|
||||
@router.post("/categories", status_code=201)
|
||||
def create_category(data: CategoryCreate, db: Session = Depends(get_db)):
|
||||
existing = db.query(CollectorCategory).filter(CollectorCategory.name == data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(400, "类别名已存在")
|
||||
cat = CollectorCategory(**data.model_dump())
|
||||
db.add(cat)
|
||||
db.commit()
|
||||
db.refresh(cat)
|
||||
return cat.to_dict()
|
||||
|
||||
@router.put("/categories/{cat_id}")
|
||||
def update_category(cat_id: int, data: CategoryUpdate, db: Session = Depends(get_db)):
|
||||
cat = db.query(CollectorCategory).filter(CollectorCategory.id == cat_id).first()
|
||||
if not cat:
|
||||
raise HTTPException(404, "类别不存在")
|
||||
for k, v in data.model_dump(exclude_unset=True).items():
|
||||
setattr(cat, k, v)
|
||||
db.commit()
|
||||
db.refresh(cat)
|
||||
return cat.to_dict()
|
||||
|
||||
@router.delete("/categories/{cat_id}")
|
||||
def delete_category(cat_id: int, db: Session = Depends(get_db)):
|
||||
cat = db.query(CollectorCategory).filter(CollectorCategory.id == cat_id).first()
|
||||
if not cat:
|
||||
raise HTTPException(404, "类别不存在")
|
||||
db.delete(cat)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
# ---------- Sources ----------
|
||||
@router.get("/sources")
|
||||
def list_sources(category_id: Optional[int] = None, db: Session = Depends(get_db)):
|
||||
q = db.query(CollectorSource).order_by(CollectorSource.sort_order)
|
||||
if category_id is not None:
|
||||
q = q.filter(CollectorSource.category_id == category_id)
|
||||
return [s.to_dict() for s in q.all()]
|
||||
|
||||
@router.post("/sources", status_code=201)
|
||||
def create_source(data: SourceCreate, db: Session = Depends(get_db)):
|
||||
src = CollectorSource(**data.model_dump())
|
||||
db.add(src)
|
||||
db.commit()
|
||||
db.refresh(src)
|
||||
return src.to_dict()
|
||||
|
||||
@router.put("/sources/{src_id}")
|
||||
def update_source(src_id: int, data: SourceUpdate, db: Session = Depends(get_db)):
|
||||
src = db.query(CollectorSource).filter(CollectorSource.id == src_id).first()
|
||||
if not src:
|
||||
raise HTTPException(404, "信息源不存在")
|
||||
for k, v in data.model_dump(exclude_unset=True).items():
|
||||
setattr(src, k, v)
|
||||
db.commit()
|
||||
db.refresh(src)
|
||||
return src.to_dict()
|
||||
|
||||
@router.delete("/sources/{src_id}")
|
||||
def delete_source(src_id: int, db: Session = Depends(get_db)):
|
||||
src = db.query(CollectorSource).filter(CollectorSource.id == src_id).first()
|
||||
if not src:
|
||||
raise HTTPException(404, "信息源不存在")
|
||||
db.delete(src)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
Reference in New Issue
Block a user