project.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. """
  2. Project API router.
  3. Provides CRUD endpoints for project management.
  4. """
  5. import uuid
  6. from typing import List
  7. from fastapi import APIRouter, HTTPException, status
  8. from database import get_db_connection
  9. from schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse
  10. from models import Project
  11. router = APIRouter(
  12. prefix="/api/projects",
  13. tags=["projects"]
  14. )
  15. @router.get("", response_model=List[ProjectResponse])
  16. async def list_projects():
  17. """
  18. List all projects.
  19. Returns a list of all projects with their task counts.
  20. """
  21. with get_db_connection() as conn:
  22. cursor = conn.cursor()
  23. # Get all projects with task counts
  24. cursor.execute("""
  25. SELECT
  26. p.id,
  27. p.name,
  28. p.description,
  29. p.config,
  30. p.created_at,
  31. COUNT(t.id) as task_count
  32. FROM projects p
  33. LEFT JOIN tasks t ON p.id = t.project_id
  34. GROUP BY p.id
  35. ORDER BY p.created_at DESC
  36. """)
  37. rows = cursor.fetchall()
  38. projects = []
  39. for row in rows:
  40. projects.append(ProjectResponse(
  41. id=row["id"],
  42. name=row["name"],
  43. description=row["description"] or "",
  44. config=row["config"],
  45. created_at=row["created_at"],
  46. task_count=row["task_count"]
  47. ))
  48. return projects
  49. @router.post("", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
  50. async def create_project(project: ProjectCreate):
  51. """
  52. Create a new project.
  53. Args:
  54. project: Project creation data
  55. Returns:
  56. Created project with generated ID
  57. """
  58. # Generate unique ID
  59. project_id = f"proj_{uuid.uuid4().hex[:12]}"
  60. with get_db_connection() as conn:
  61. cursor = conn.cursor()
  62. # Insert new project
  63. cursor.execute("""
  64. INSERT INTO projects (id, name, description, config)
  65. VALUES (?, ?, ?, ?)
  66. """, (
  67. project_id,
  68. project.name,
  69. project.description,
  70. project.config
  71. ))
  72. # Fetch the created project
  73. cursor.execute("""
  74. SELECT id, name, description, config, created_at
  75. FROM projects
  76. WHERE id = ?
  77. """, (project_id,))
  78. row = cursor.fetchone()
  79. return ProjectResponse(
  80. id=row["id"],
  81. name=row["name"],
  82. description=row["description"] or "",
  83. config=row["config"],
  84. created_at=row["created_at"],
  85. task_count=0
  86. )
  87. @router.get("/{project_id}", response_model=ProjectResponse)
  88. async def get_project(project_id: str):
  89. """
  90. Get project by ID.
  91. Args:
  92. project_id: Project unique identifier
  93. Returns:
  94. Project details with task count
  95. Raises:
  96. HTTPException: 404 if project not found
  97. """
  98. with get_db_connection() as conn:
  99. cursor = conn.cursor()
  100. # Get project with task count
  101. cursor.execute("""
  102. SELECT
  103. p.id,
  104. p.name,
  105. p.description,
  106. p.config,
  107. p.created_at,
  108. COUNT(t.id) as task_count
  109. FROM projects p
  110. LEFT JOIN tasks t ON p.id = t.project_id
  111. WHERE p.id = ?
  112. GROUP BY p.id
  113. """, (project_id,))
  114. row = cursor.fetchone()
  115. if not row:
  116. raise HTTPException(
  117. status_code=status.HTTP_404_NOT_FOUND,
  118. detail=f"Project with id '{project_id}' not found"
  119. )
  120. return ProjectResponse(
  121. id=row["id"],
  122. name=row["name"],
  123. description=row["description"] or "",
  124. config=row["config"],
  125. created_at=row["created_at"],
  126. task_count=row["task_count"]
  127. )
  128. @router.put("/{project_id}", response_model=ProjectResponse)
  129. async def update_project(project_id: str, project: ProjectUpdate):
  130. """
  131. Update an existing project.
  132. Args:
  133. project_id: Project unique identifier
  134. project: Project update data
  135. Returns:
  136. Updated project details
  137. Raises:
  138. HTTPException: 404 if project not found
  139. """
  140. with get_db_connection() as conn:
  141. cursor = conn.cursor()
  142. # Check if project exists
  143. cursor.execute("SELECT id FROM projects WHERE id = ?", (project_id,))
  144. if not cursor.fetchone():
  145. raise HTTPException(
  146. status_code=status.HTTP_404_NOT_FOUND,
  147. detail=f"Project with id '{project_id}' not found"
  148. )
  149. # Build update query dynamically based on provided fields
  150. update_fields = []
  151. update_values = []
  152. if project.name is not None:
  153. update_fields.append("name = ?")
  154. update_values.append(project.name)
  155. if project.description is not None:
  156. update_fields.append("description = ?")
  157. update_values.append(project.description)
  158. if project.config is not None:
  159. update_fields.append("config = ?")
  160. update_values.append(project.config)
  161. if not update_fields:
  162. # No fields to update, just return current project
  163. cursor.execute("""
  164. SELECT
  165. p.id,
  166. p.name,
  167. p.description,
  168. p.config,
  169. p.created_at,
  170. COUNT(t.id) as task_count
  171. FROM projects p
  172. LEFT JOIN tasks t ON p.id = t.project_id
  173. WHERE p.id = ?
  174. GROUP BY p.id
  175. """, (project_id,))
  176. row = cursor.fetchone()
  177. return ProjectResponse(
  178. id=row["id"],
  179. name=row["name"],
  180. description=row["description"] or "",
  181. config=row["config"],
  182. created_at=row["created_at"],
  183. task_count=row["task_count"]
  184. )
  185. # Execute update
  186. update_values.append(project_id)
  187. cursor.execute(f"""
  188. UPDATE projects
  189. SET {', '.join(update_fields)}
  190. WHERE id = ?
  191. """, update_values)
  192. # Fetch and return updated project
  193. cursor.execute("""
  194. SELECT
  195. p.id,
  196. p.name,
  197. p.description,
  198. p.config,
  199. p.created_at,
  200. COUNT(t.id) as task_count
  201. FROM projects p
  202. LEFT JOIN tasks t ON p.id = t.project_id
  203. WHERE p.id = ?
  204. GROUP BY p.id
  205. """, (project_id,))
  206. row = cursor.fetchone()
  207. return ProjectResponse(
  208. id=row["id"],
  209. name=row["name"],
  210. description=row["description"] or "",
  211. config=row["config"],
  212. created_at=row["created_at"],
  213. task_count=row["task_count"]
  214. )
  215. @router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
  216. async def delete_project(project_id: str):
  217. """
  218. Delete a project and all associated tasks.
  219. Args:
  220. project_id: Project unique identifier
  221. Raises:
  222. HTTPException: 404 if project not found
  223. """
  224. with get_db_connection() as conn:
  225. cursor = conn.cursor()
  226. # Check if project exists
  227. cursor.execute("SELECT id FROM projects WHERE id = ?", (project_id,))
  228. if not cursor.fetchone():
  229. raise HTTPException(
  230. status_code=status.HTTP_404_NOT_FOUND,
  231. detail=f"Project with id '{project_id}' not found"
  232. )
  233. # Delete project (cascade will delete tasks and annotations)
  234. cursor.execute("DELETE FROM projects WHERE id = ?", (project_id,))
  235. return None