""" 项目管理相关 API """ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, or_ from typing import List import uuid from app.core.database import get_db from app.core.deps import get_current_user from app.models.user import User from app.models.project import Project, ProjectMember, ProjectMemberRole from app.schemas.project import ( ProjectCreate, ProjectUpdate, ProjectResponse, ProjectMemberAdd, ProjectMemberUpdate, ProjectMemberResponse, ProjectShareSettings, ProjectShareInfo, ) from app.schemas.response import success_response from app.services.storage import storage_service router = APIRouter() @router.get("/", response_model=dict) async def get_my_projects( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """获取我的项目列表(包括创建的和协作的)""" # 查询我创建的项目 owned_result = await db.execute( select(Project).where(Project.owner_id == current_user.id, Project.status == 1) ) owned_projects = owned_result.scalars().all() # 查询我协作的项目 member_result = await db.execute( select(Project) .join(ProjectMember, ProjectMember.project_id == Project.id) .where( ProjectMember.user_id == current_user.id, Project.owner_id != current_user.id, Project.status == 1 ) ) member_projects = member_result.scalars().all() # 合并结果 all_projects = owned_projects + member_projects projects_data = [ProjectResponse.from_orm(p).dict() for p in all_projects] return success_response(data=projects_data) @router.post("/", response_model=dict) async def create_project( project_in: ProjectCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """创建新项目""" # 生成 UUID 作为存储键 storage_key = str(uuid.uuid4()) # 创建项目记录 db_project = Project( name=project_in.name, description=project_in.description, storage_key=storage_key, owner_id=current_user.id, is_public=project_in.is_public, status=1, ) db.add(db_project) await db.commit() await db.refresh(db_project) # 创建物理文件夹结构 try: storage_service.create_project_structure(storage_key) except Exception as e: # 如果文件夹创建失败,回滚数据库记录 await db.delete(db_project) await db.commit() raise HTTPException(status_code=500, detail=f"项目文件夹创建失败: {str(e)}") # 添加项目所有者为管理员成员 db_member = ProjectMember( project_id=db_project.id, user_id=current_user.id, role=ProjectMemberRole.ADMIN, ) db.add(db_member) await db.commit() project_data = ProjectResponse.from_orm(db_project) return success_response(data=project_data.dict(), message="项目创建成功") @router.get("/{project_id}", response_model=dict) async def get_project( project_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """获取项目详情""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 检查权限(项目所有者或成员可访问) if project.owner_id != current_user.id: member_result = await db.execute( select(ProjectMember).where( ProjectMember.project_id == project_id, ProjectMember.user_id == current_user.id ) ) member = member_result.scalar_one_or_none() if not member and project.is_public != 1: raise HTTPException(status_code=403, detail="无权访问该项目") # 增加访问次数 project.visit_count += 1 await db.commit() project_data = ProjectResponse.from_orm(project) return success_response(data=project_data.dict()) @router.put("/{project_id}", response_model=dict) async def update_project( project_id: int, project_in: ProjectUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """更新项目信息""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 只有项目所有者可以更新 if project.owner_id != current_user.id: raise HTTPException(status_code=403, detail="无权修改该项目") # 更新字段 update_data = project_in.dict(exclude_unset=True) for field, value in update_data.items(): setattr(project, field, value) await db.commit() await db.refresh(project) project_data = ProjectResponse.from_orm(project) return success_response(data=project_data.dict(), message="项目更新成功") @router.delete("/{project_id}", response_model=dict) async def delete_project( project_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """删除项目(归档)""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 只有项目所有者可以删除 if project.owner_id != current_user.id: raise HTTPException(status_code=403, detail="无权删除该项目") # 软删除(归档) project.status = 0 await db.commit() return success_response(message="项目已归档") @router.get("/{project_id}/members", response_model=dict) async def get_project_members( project_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """获取项目成员列表""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 检查权限 if project.owner_id != current_user.id: member_result = await db.execute( select(ProjectMember).where( ProjectMember.project_id == project_id, ProjectMember.user_id == current_user.id ) ) member = member_result.scalar_one_or_none() if not member: raise HTTPException(status_code=403, detail="无权访问该项目") # 查询成员列表 members_result = await db.execute( select(ProjectMember).where(ProjectMember.project_id == project_id) ) members = members_result.scalars().all() members_data = [ProjectMemberResponse.from_orm(m).dict() for m in members] return success_response(data=members_data) @router.post("/{project_id}/members", response_model=dict) async def add_project_member( project_id: int, member_in: ProjectMemberAdd, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """添加项目成员""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 只有项目所有者和管理员可以添加成员 if project.owner_id != current_user.id: member_result = await db.execute( select(ProjectMember).where( ProjectMember.project_id == project_id, ProjectMember.user_id == current_user.id, ProjectMember.role == ProjectMemberRole.ADMIN ) ) member = member_result.scalar_one_or_none() if not member: raise HTTPException(status_code=403, detail="无权添加成员") # 检查用户是否已是成员 existing_result = await db.execute( select(ProjectMember).where( ProjectMember.project_id == project_id, ProjectMember.user_id == member_in.user_id ) ) existing_member = existing_result.scalar_one_or_none() if existing_member: raise HTTPException(status_code=400, detail="用户已是项目成员") # 添加成员 db_member = ProjectMember( project_id=project_id, user_id=member_in.user_id, role=member_in.role, invited_by=current_user.id, ) db.add(db_member) await db.commit() await db.refresh(db_member) member_data = ProjectMemberResponse.from_orm(db_member) return success_response(data=member_data.dict(), message="成员添加成功") @router.get("/{project_id}/share", response_model=dict) async def get_project_share_info( project_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """获取项目分享信息""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 只有项目所有者可以获取分享信息 if project.owner_id != current_user.id: raise HTTPException(status_code=403, detail="只有项目所有者可以查看分享信息") # 构建分享链接 share_url = f"/preview/{project_id}" share_info = ProjectShareInfo( share_url=share_url, has_password=bool(project.access_pass), access_pass=project.access_pass # 返回实际密码给项目所有者 ) return success_response(data=share_info.dict()) @router.post("/{project_id}/share/settings", response_model=dict) async def update_share_settings( project_id: int, settings: ProjectShareSettings, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """更新分享设置(设置或取消访问密码)""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: raise HTTPException(status_code=404, detail="项目不存在") # 只有项目所有者可以修改分享设置 if project.owner_id != current_user.id: raise HTTPException(status_code=403, detail="只有项目所有者可以修改分享设置") # 更新访问密码 project.access_pass = settings.access_pass await db.commit() message = "访问密码已取消" if not settings.access_pass else "访问密码已设置" return success_response(message=message)