Files
hiderfong 8b2bc84399 feat: implement full RBAC role-based access control
Backend:
- deps.py: add require_admin, require_manager, require_labeler, require_guest_or_above
- user.py: all write endpoints require admin
- datasource.py: write/sync endpoints require admin
- metadata.py: sync endpoint requires admin
- classification.py: category/rule write requires admin; results query requires guest+ with data isolation
- project.py: GET requires manager with created_by filtering; DELETE checks ownership
- task.py: my-tasks requires labeler with assignee_id filtering; create-task requires manager
- dashboard.py: requires guest_or_above
- report.py: requires guest_or_above
- project_service: list_projects adds created_by filter; list_results adds project_ids filter

Frontend:
- stores/user.ts: add hasRole, hasAnyRole, isAdmin, isManager, isLabeler, isSuperadmin
- router/index.ts: add roles to route meta; beforeEach checks role permissions
- Layout.vue: filter menu routes by user roles
- System.vue: hide add/edit/delete buttons for non-admins
- DataSource.vue: hide add/edit/delete/sync buttons for non-admins
- Project.vue: hide add/delete buttons for non-admins
2026-04-23 12:09:32 +08:00

126 lines
4.3 KiB
Python

from typing import Optional, List, Tuple
from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from app.models.project import ClassificationProject, ClassificationTask, ClassificationResult
from app.models.classification import Category, DataLevel
from app.models.metadata import DataColumn, DataTable, Database as MetaDatabase
def get_project(db: Session, project_id: int) -> Optional[ClassificationProject]:
return db.query(ClassificationProject).filter(ClassificationProject.id == project_id).first()
def list_projects(
db: Session, keyword: Optional[str] = None, page: int = 1, page_size: int = 20, created_by: Optional[int] = None
) -> Tuple[List[ClassificationProject], int]:
query = db.query(ClassificationProject)
if keyword:
query = query.filter(ClassificationProject.name.contains(keyword))
if created_by:
query = query.filter(ClassificationProject.created_by == created_by)
total = query.count()
items = query.order_by(ClassificationProject.created_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
return items, total
def create_project(db: Session, name: str, template_id: int, created_by: int, **kwargs) -> ClassificationProject:
db_obj = ClassificationProject(
name=name,
template_id=template_id,
created_by=created_by,
**kwargs,
)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
def update_project(db: Session, db_obj: ClassificationProject, **kwargs) -> ClassificationProject:
for k, v in kwargs.items():
if v is not None:
setattr(db_obj, k, v)
db.commit()
db.refresh(db_obj)
return db_obj
def delete_project(db: Session, project_id: int) -> None:
db_obj = get_project(db, project_id)
if not db_obj:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
db.delete(db_obj)
db.commit()
def get_project_stats(db: Session, project_id: int) -> dict:
total = db.query(ClassificationResult).filter(ClassificationResult.project_id == project_id).count()
auto_count = db.query(ClassificationResult).filter(
ClassificationResult.project_id == project_id,
ClassificationResult.source == "auto",
).count()
manual_count = db.query(ClassificationResult).filter(
ClassificationResult.project_id == project_id,
ClassificationResult.source == "manual",
).count()
reviewed_count = db.query(ClassificationResult).filter(
ClassificationResult.project_id == project_id,
ClassificationResult.status == "reviewed",
).count()
return {
"total": total,
"auto": auto_count,
"manual": manual_count,
"reviewed": reviewed_count,
}
def list_results(
db: Session,
project_id: Optional[int] = None,
table_id: Optional[int] = None,
status: Optional[str] = None,
keyword: Optional[str] = None,
page: int = 1,
page_size: int = 50,
project_ids: Optional[List[int]] = None,
) -> Tuple[List[ClassificationResult], int]:
query = db.query(ClassificationResult)
if project_id:
query = query.filter(ClassificationResult.project_id == project_id)
if project_ids:
query = query.filter(ClassificationResult.project_id.in_(project_ids))
if table_id:
query = query.join(DataColumn).filter(DataColumn.table_id == table_id)
if status:
query = query.filter(ClassificationResult.status == status)
if keyword:
query = query.join(DataColumn).filter(
(DataColumn.name.contains(keyword)) | (DataColumn.comment.contains(keyword))
)
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return items, total
def update_result_label(
db: Session,
result_id: int,
category_id: int,
level_id: int,
labeler_id: int,
) -> ClassificationResult:
result = db.query(ClassificationResult).filter(ClassificationResult.id == result_id).first()
if not result:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="结果不存在")
result.category_id = category_id
result.level_id = level_id
result.labeler_id = labeler_id
result.source = "manual"
result.status = "manual"
result.label_time = __import__('datetime').datetime.utcnow()
db.commit()
db.refresh(result)
return result