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:
@@ -3,16 +3,19 @@ from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
import bcrypt
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from ..database import get_db
|
||||
from ..models import User
|
||||
from ..models import User, Topic, SystemConfig
|
||||
from ..schemas import UserCreate, UserUpdate, UserResponse
|
||||
from ..core.audit_logger import audit_log
|
||||
import json
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
||||
|
||||
def get_current_admin(request: Request, db: Session = Depends(get_db)):
|
||||
"""依赖项:验证管理员权限"""
|
||||
from .auth import verify_token
|
||||
from .auth import verify_token, org_filter
|
||||
auth_header = request.headers.get("Authorization")
|
||||
if not auth_header or not auth_header.startswith("Bearer "):
|
||||
raise HTTPException(status_code=401, detail="未提供认证令牌")
|
||||
@@ -29,7 +32,11 @@ def list_users(
|
||||
admin_user: User = Depends(get_current_admin)
|
||||
):
|
||||
"""获取用户列表(管理员)"""
|
||||
users = db.query(User).all()
|
||||
q = db.query(User)
|
||||
of = org_filter(admin_user, User)
|
||||
if of is not True:
|
||||
q = q.filter(of)
|
||||
users = q.all()
|
||||
return [UserResponse.from_orm(u) for u in users]
|
||||
|
||||
@router.post("/users", response_model=UserResponse)
|
||||
@@ -51,7 +58,8 @@ def create_user(
|
||||
user = User(
|
||||
username=user_data.username,
|
||||
password_hash=hashed_password,
|
||||
role=user_data.role or "user"
|
||||
role=user_data.role or "user",
|
||||
org_id=user_data.org_id or admin_user.org_id or "default"
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
@@ -144,3 +152,115 @@ def delete_user(
|
||||
db=db
|
||||
)
|
||||
return {"message": "删除成功"}
|
||||
|
||||
# ---------- Org Management ----------
|
||||
|
||||
class OrgCreate(BaseModel):
|
||||
org_id: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
|
||||
class OrgUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
@router.get("/orgs")
|
||||
def list_orgs(
|
||||
db: Session = Depends(get_db),
|
||||
admin_user: User = Depends(get_current_admin)
|
||||
):
|
||||
distinct_orgs = db.query(User.org_id).distinct().all()
|
||||
result = []
|
||||
for (org_id,) in distinct_orgs:
|
||||
if not org_id:
|
||||
continue
|
||||
cfg = db.query(SystemConfig).filter(SystemConfig.key == f"org:{org_id}").first()
|
||||
meta = json.loads(cfg.value) if cfg and cfg.value else {}
|
||||
user_count = db.query(User).filter(User.org_id == org_id).count()
|
||||
topic_count = db.query(Topic).filter(Topic.org_id == org_id).count()
|
||||
result.append({
|
||||
"org_id": org_id,
|
||||
"name": meta.get("name", org_id),
|
||||
"description": meta.get("description", ""),
|
||||
"user_count": user_count,
|
||||
"topic_count": topic_count,
|
||||
"is_default": org_id == "default"
|
||||
})
|
||||
return result
|
||||
|
||||
@router.post("/orgs")
|
||||
def create_org(
|
||||
data: OrgCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
admin_user: User = Depends(get_current_admin)
|
||||
):
|
||||
existing = db.query(SystemConfig).filter(SystemConfig.key == f"org:{data.org_id}").first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="组织已存在")
|
||||
cfg = SystemConfig(
|
||||
key=f"org:{data.org_id}",
|
||||
value=json.dumps({"name": data.name, "description": data.description or ""}, ensure_ascii=False),
|
||||
description=f"Organization: {data.org_id}"
|
||||
)
|
||||
db.add(cfg)
|
||||
db.commit()
|
||||
audit_log(
|
||||
action="create_org", user=admin_user,
|
||||
resource_type="org", resource_id=data.org_id,
|
||||
details=data.model_dump(),
|
||||
ip_address=request.client.host if request.client else None,
|
||||
user_agent=request.headers.get("user-agent", ""),
|
||||
db=db
|
||||
)
|
||||
return {"org_id": data.org_id, "name": data.name, "description": data.description or ""}
|
||||
|
||||
@router.put("/orgs/{org_id}")
|
||||
def update_org(
|
||||
org_id: str,
|
||||
data: OrgUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
admin_user: User = Depends(get_current_admin)
|
||||
):
|
||||
cfg = db.query(SystemConfig).filter(SystemConfig.key == f"org:{org_id}").first()
|
||||
if not cfg:
|
||||
raise HTTPException(status_code=404, detail="组织不存在")
|
||||
meta = json.loads(cfg.value) if cfg.value else {}
|
||||
if data.name is not None:
|
||||
meta["name"] = data.name
|
||||
if data.description is not None:
|
||||
meta["description"] = data.description
|
||||
cfg.value = json.dumps(meta, ensure_ascii=False)
|
||||
db.commit()
|
||||
audit_log(
|
||||
action="update_org", user=admin_user,
|
||||
resource_type="org", resource_id=org_id,
|
||||
details=data.model_dump(exclude_unset=True),
|
||||
ip_address=request.client.host if request.client else None,
|
||||
user_agent=request.headers.get("user-agent", ""),
|
||||
db=db
|
||||
)
|
||||
return {"org_id": org_id, **meta}
|
||||
|
||||
@router.delete("/orgs/{org_id}")
|
||||
def delete_org(
|
||||
org_id: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
admin_user: User = Depends(get_current_admin)
|
||||
):
|
||||
if org_id == "default":
|
||||
raise HTTPException(status_code=400, detail="不能删除默认组织")
|
||||
cfg = db.query(SystemConfig).filter(SystemConfig.key == f"org:{org_id}").first()
|
||||
if cfg:
|
||||
db.delete(cfg)
|
||||
db.commit()
|
||||
audit_log(
|
||||
action="delete_org", user=admin_user,
|
||||
resource_type="org", resource_id=org_id,
|
||||
ip_address=request.client.host if request.client else None,
|
||||
user_agent=request.headers.get("user-agent", ""),
|
||||
db=db
|
||||
)
|
||||
return {"message": "删除成功"}
|
||||
|
||||
Reference in New Issue
Block a user