| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- """
- Project API router.
- Provides CRUD endpoints for project management.
- """
- import uuid
- from typing import List
- from fastapi import APIRouter, HTTPException, status, Request
- from database import get_db_connection
- from schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse
- from models import Project
- router = APIRouter(
- prefix="/api/projects",
- tags=["projects"]
- )
- @router.get("", response_model=List[ProjectResponse])
- async def list_projects(request: Request):
- """
- List all projects.
- Returns a list of all projects with their task counts.
-
- Requires authentication.
- """
- with get_db_connection() as conn:
- cursor = conn.cursor()
-
- # Get all projects with task counts
- cursor.execute("""
- SELECT
- p.id,
- p.name,
- p.description,
- p.config,
- p.created_at,
- COUNT(t.id) as task_count
- FROM projects p
- LEFT JOIN tasks t ON p.id = t.project_id
- GROUP BY p.id
- ORDER BY p.created_at DESC
- """)
-
- rows = cursor.fetchall()
-
- projects = []
- for row in rows:
- projects.append(ProjectResponse(
- id=row["id"],
- name=row["name"],
- description=row["description"] or "",
- config=row["config"],
- created_at=row["created_at"],
- task_count=row["task_count"]
- ))
-
- return projects
- @router.post("", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
- async def create_project(request: Request, project: ProjectCreate):
- """
- Create a new project.
-
- Args:
- request: FastAPI Request object (contains user info)
- project: Project creation data
-
- Returns:
- Created project with generated ID
-
- Requires authentication.
- """
- # Generate unique ID
- project_id = f"proj_{uuid.uuid4().hex[:12]}"
-
- with get_db_connection() as conn:
- cursor = conn.cursor()
-
- # Insert new project
- cursor.execute("""
- INSERT INTO projects (id, name, description, config)
- VALUES (?, ?, ?, ?)
- """, (
- project_id,
- project.name,
- project.description,
- project.config
- ))
-
- # Fetch the created project
- cursor.execute("""
- SELECT id, name, description, config, created_at
- FROM projects
- WHERE id = ?
- """, (project_id,))
-
- row = cursor.fetchone()
-
- return ProjectResponse(
- id=row["id"],
- name=row["name"],
- description=row["description"] or "",
- config=row["config"],
- created_at=row["created_at"],
- task_count=0
- )
- @router.get("/{project_id}", response_model=ProjectResponse)
- async def get_project(request: Request, project_id: str):
- """
- Get project by ID.
-
- Args:
- request: FastAPI Request object (contains user info)
- project_id: Project unique identifier
-
- Returns:
- Project details with task count
-
- Raises:
- HTTPException: 404 if project not found
-
- Requires authentication.
- """
- with get_db_connection() as conn:
- cursor = conn.cursor()
-
- # Get project with task count
- cursor.execute("""
- SELECT
- p.id,
- p.name,
- p.description,
- p.config,
- p.created_at,
- COUNT(t.id) as task_count
- FROM projects p
- LEFT JOIN tasks t ON p.id = t.project_id
- WHERE p.id = ?
- GROUP BY p.id
- """, (project_id,))
-
- row = cursor.fetchone()
-
- if not row:
- raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND,
- detail=f"Project with id '{project_id}' not found"
- )
-
- return ProjectResponse(
- id=row["id"],
- name=row["name"],
- description=row["description"] or "",
- config=row["config"],
- created_at=row["created_at"],
- task_count=row["task_count"]
- )
- @router.put("/{project_id}", response_model=ProjectResponse)
- async def update_project(request: Request, project_id: str, project: ProjectUpdate):
- """
- Update an existing project.
-
- Args:
- request: FastAPI Request object (contains user info)
- project_id: Project unique identifier
- project: Project update data
-
- Returns:
- Updated project details
-
- Raises:
- HTTPException: 404 if project not found
-
- Requires authentication.
- """
- with get_db_connection() as conn:
- cursor = conn.cursor()
-
- # Check if project exists
- cursor.execute("SELECT id FROM projects WHERE id = ?", (project_id,))
- if not cursor.fetchone():
- raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND,
- detail=f"Project with id '{project_id}' not found"
- )
-
- # Build update query dynamically based on provided fields
- update_fields = []
- update_values = []
-
- if project.name is not None:
- update_fields.append("name = ?")
- update_values.append(project.name)
-
- if project.description is not None:
- update_fields.append("description = ?")
- update_values.append(project.description)
-
- if project.config is not None:
- update_fields.append("config = ?")
- update_values.append(project.config)
-
- if not update_fields:
- # No fields to update, just return current project
- cursor.execute("""
- SELECT
- p.id,
- p.name,
- p.description,
- p.config,
- p.created_at,
- COUNT(t.id) as task_count
- FROM projects p
- LEFT JOIN tasks t ON p.id = t.project_id
- WHERE p.id = ?
- GROUP BY p.id
- """, (project_id,))
- row = cursor.fetchone()
- return ProjectResponse(
- id=row["id"],
- name=row["name"],
- description=row["description"] or "",
- config=row["config"],
- created_at=row["created_at"],
- task_count=row["task_count"]
- )
-
- # Execute update
- update_values.append(project_id)
- cursor.execute(f"""
- UPDATE projects
- SET {', '.join(update_fields)}
- WHERE id = ?
- """, update_values)
-
- # Fetch and return updated project
- cursor.execute("""
- SELECT
- p.id,
- p.name,
- p.description,
- p.config,
- p.created_at,
- COUNT(t.id) as task_count
- FROM projects p
- LEFT JOIN tasks t ON p.id = t.project_id
- WHERE p.id = ?
- GROUP BY p.id
- """, (project_id,))
-
- row = cursor.fetchone()
- return ProjectResponse(
- id=row["id"],
- name=row["name"],
- description=row["description"] or "",
- config=row["config"],
- created_at=row["created_at"],
- task_count=row["task_count"]
- )
- @router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
- async def delete_project(request: Request, project_id: str):
- """
- Delete a project and all associated tasks.
-
- Args:
- request: FastAPI Request object (contains user info)
- project_id: Project unique identifier
-
- Raises:
- HTTPException: 404 if project not found
- HTTPException: 403 if user is not admin
-
- Requires authentication and admin role.
- """
- # Check if user has admin role
- user = request.state.user
- if user["role"] != "admin":
- raise HTTPException(
- status_code=status.HTTP_403_FORBIDDEN,
- detail="只有管理员可以删除项目"
- )
-
- with get_db_connection() as conn:
- cursor = conn.cursor()
-
- # Check if project exists
- cursor.execute("SELECT id FROM projects WHERE id = ?", (project_id,))
- if not cursor.fetchone():
- raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND,
- detail=f"Project with id '{project_id}' not found"
- )
-
- # Delete project (cascade will delete tasks and annotations)
- cursor.execute("DELETE FROM projects WHERE id = ?", (project_id,))
-
- return None
|