收敛用户角色并共享项目库

- 后端限制系统只保留默认 admin 管理员,新建用户固定为标注员,并拒绝观察员或额外管理员角色。

- 将项目、帧、媒体解析、AI 标注、任务、Dashboard 和导出接口改为共享项目库访问,标注员具备同等项目管理和标注能力。

- 前端用户管理移除角色选择和观察员入口,只展示唯一管理员与标注员状态。

- 更新后端/前端测试,覆盖唯一 admin、旧 viewer 归一为标注员、用户删除和共享项目库访问。

- 同步更新 AGENTS 与 doc 文档中的角色权限、共享项目库和测试计划说明。
This commit is contained in:
2026-05-04 05:20:28 +08:00
parent 02635abab1
commit 523beeb446
21 changed files with 214 additions and 172 deletions

View File

@@ -16,12 +16,12 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["Projects"])
def _next_project_copy_name(db: Session, owner_user_id: int, source_name: str) -> str:
def _next_project_copy_name(db: Session, source_name: str) -> str:
base_name = f"{source_name} 副本"
existing_names = {
row[0]
for row in db.query(Project.name)
.filter(Project.owner_user_id == owner_user_id, Project.name.like(f"{base_name}%"))
.filter(Project.name.like(f"{base_name}%"))
.all()
}
if base_name not in existing_names:
@@ -76,7 +76,6 @@ def list_projects(
"""Retrieve a paginated list of projects."""
projects = (
db.query(Project)
.filter(Project.owner_user_id == current_user.id)
.offset(skip)
.limit(limit)
.all()
@@ -97,10 +96,7 @@ def get_project(
current_user: User = Depends(get_current_user),
) -> Project:
"""Retrieve a project by its ID."""
project = db.query(Project).filter(
Project.id == project_id,
Project.owner_user_id == current_user.id,
).first()
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return _prepare_project_response(project)
@@ -119,16 +115,13 @@ def copy_project(
current_user: User = Depends(require_editor),
) -> Project:
"""Copy a project. Reset copies media/frame sequence; full also copies annotations and mask metadata."""
source = db.query(Project).filter(
Project.id == project_id,
Project.owner_user_id == current_user.id,
).first()
source = db.query(Project).filter(Project.id == project_id).first()
if not source:
raise HTTPException(status_code=404, detail="Project not found")
next_name = (payload.name or "").strip() if payload.name is not None else ""
if not next_name:
next_name = _next_project_copy_name(db, current_user.id, source.name)
next_name = _next_project_copy_name(db, source.name)
copied = Project(
name=next_name,
@@ -196,10 +189,7 @@ def update_project(
current_user: User = Depends(require_editor),
) -> Project:
"""Update project fields partially."""
project = db.query(Project).filter(
Project.id == project_id,
Project.owner_user_id == current_user.id,
).first()
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
@@ -227,10 +217,7 @@ def delete_project(
current_user: User = Depends(require_editor),
) -> None:
"""Delete a project and all related frames and annotations."""
project = db.query(Project).filter(
Project.id == project_id,
Project.owner_user_id == current_user.id,
).first()
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
@@ -255,10 +242,7 @@ def create_frame(
current_user: User = Depends(require_editor),
) -> Frame:
"""Register a new frame under a project."""
project = db.query(Project).filter(
Project.id == project_id,
Project.owner_user_id == current_user.id,
).first()
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
@@ -282,10 +266,7 @@ def list_frames(
current_user: User = Depends(get_current_user),
) -> List[Frame]:
"""Retrieve all frames belonging to a project."""
project = db.query(Project).filter(
Project.id == project_id,
Project.owner_user_id == current_user.id,
).first()
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
@@ -320,7 +301,6 @@ def get_frame(
.filter(
Frame.project_id == project_id,
Frame.id == frame_id,
Project.owner_user_id == current_user.id,
)
.first()
)