CodeReview/backend/app/api/v1/endpoints/members.py

215 lines
6.0 KiB
Python

from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload
from pydantic import BaseModel
from datetime import datetime
from app.api import deps
from app.db.session import get_db
from app.models.project import Project, ProjectMember
from app.models.user import User
router = APIRouter()
# Schemas
class UserSchema(BaseModel):
id: str
email: Optional[str] = None
full_name: Optional[str] = None
avatar_url: Optional[str] = None
role: Optional[str] = None
class Config:
from_attributes = True
class ProjectMemberSchema(BaseModel):
id: str
project_id: str
user_id: str
role: str
permissions: Optional[str] = None
joined_at: datetime
created_at: datetime
user: Optional[UserSchema] = None
class Config:
from_attributes = True
class AddMemberRequest(BaseModel):
user_id: str
role: str = "member"
class UpdateMemberRequest(BaseModel):
role: Optional[str] = None
permissions: Optional[str] = None
@router.get("/{project_id}/members", response_model=List[ProjectMemberSchema])
async def get_project_members(
project_id: str,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Get all members of a project.
"""
# Verify project exists
project = await db.get(Project, project_id)
if not project:
raise HTTPException(status_code=404, detail="项目不存在")
result = await db.execute(
select(ProjectMember)
.options(selectinload(ProjectMember.user))
.where(ProjectMember.project_id == project_id)
.order_by(ProjectMember.joined_at.desc())
)
return result.scalars().all()
@router.post("/{project_id}/members", response_model=ProjectMemberSchema)
async def add_project_member(
project_id: str,
member_in: AddMemberRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Add a member to a project.
"""
# Verify project exists
project = await db.get(Project, project_id)
if not project:
raise HTTPException(status_code=404, detail="项目不存在")
# Check if user is project owner or admin
if project.owner_id != current_user.id and not current_user.is_superuser:
raise HTTPException(status_code=403, detail="权限不足")
# Check if user exists
user = await db.get(User, member_in.user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# Check if already a member
existing = await db.execute(
select(ProjectMember)
.where(
ProjectMember.project_id == project_id,
ProjectMember.user_id == member_in.user_id
)
)
if existing.scalars().first():
raise HTTPException(status_code=400, detail="用户已是项目成员")
# Create member
member = ProjectMember(
project_id=project_id,
user_id=member_in.user_id,
role=member_in.role,
permissions="{}"
)
db.add(member)
await db.commit()
await db.refresh(member)
# Reload with user relationship
result = await db.execute(
select(ProjectMember)
.options(selectinload(ProjectMember.user))
.where(ProjectMember.id == member.id)
)
return result.scalars().first()
@router.put("/{project_id}/members/{member_id}", response_model=ProjectMemberSchema)
async def update_project_member(
project_id: str,
member_id: str,
member_update: UpdateMemberRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Update a project member's role or permissions.
"""
# Verify project exists
project = await db.get(Project, project_id)
if not project:
raise HTTPException(status_code=404, detail="项目不存在")
# Check permissions
if project.owner_id != current_user.id and not current_user.is_superuser:
raise HTTPException(status_code=403, detail="权限不足")
# Get member
result = await db.execute(
select(ProjectMember)
.where(ProjectMember.id == member_id, ProjectMember.project_id == project_id)
)
member = result.scalars().first()
if not member:
raise HTTPException(status_code=404, detail="成员不存在")
# Update fields
if member_update.role:
member.role = member_update.role
if member_update.permissions:
member.permissions = member_update.permissions
await db.commit()
await db.refresh(member)
# Reload with user relationship
result = await db.execute(
select(ProjectMember)
.options(selectinload(ProjectMember.user))
.where(ProjectMember.id == member.id)
)
return result.scalars().first()
@router.delete("/{project_id}/members/{member_id}")
async def remove_project_member(
project_id: str,
member_id: str,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
Remove a member from a project.
"""
# Verify project exists
project = await db.get(Project, project_id)
if not project:
raise HTTPException(status_code=404, detail="项目不存在")
# Check permissions
if project.owner_id != current_user.id and not current_user.is_superuser:
raise HTTPException(status_code=403, detail="权限不足")
# Get member
result = await db.execute(
select(ProjectMember)
.where(ProjectMember.id == member_id, ProjectMember.project_id == project_id)
)
member = result.scalars().first()
if not member:
raise HTTPException(status_code=404, detail="成员不存在")
await db.delete(member)
await db.commit()
return {"message": "成员已移除"}