annotation.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. """
  2. Annotation API router.
  3. Provides CRUD endpoints for annotation management.
  4. """
  5. import uuid
  6. import json
  7. from typing import List, Optional
  8. from fastapi import APIRouter, HTTPException, status, Query, Request
  9. from database import get_db_connection
  10. from schemas.annotation import AnnotationCreate, AnnotationUpdate, AnnotationResponse
  11. from models import Annotation
  12. router = APIRouter(
  13. prefix="/api/annotations",
  14. tags=["annotations"]
  15. )
  16. @router.get("", response_model=List[AnnotationResponse])
  17. async def list_annotations(
  18. request: Request,
  19. task_id: Optional[str] = Query(None, description="Filter by task ID"),
  20. user_id: Optional[str] = Query(None, description="Filter by user ID")
  21. ):
  22. """
  23. List all annotations with optional filters.
  24. Args:
  25. request: FastAPI Request object (contains user info)
  26. task_id: Optional task ID filter
  27. user_id: Optional user ID filter
  28. Returns:
  29. List of annotations matching the filters
  30. Requires authentication.
  31. Note: Non-admin users can only see their own annotations unless filtering by task_id.
  32. """
  33. # Get current user
  34. user = request.state.user
  35. with get_db_connection() as conn:
  36. cursor = conn.cursor()
  37. # Build query with filters
  38. query = """
  39. SELECT
  40. id,
  41. task_id,
  42. user_id,
  43. result,
  44. created_at,
  45. updated_at
  46. FROM annotations
  47. WHERE 1=1
  48. """
  49. params = []
  50. if task_id:
  51. query += " AND task_id = ?"
  52. params.append(task_id)
  53. # Non-admin users can only see their own annotations (unless filtering by task)
  54. if user["role"] != "admin" and not task_id:
  55. query += " AND user_id = ?"
  56. params.append(user["id"])
  57. elif user_id:
  58. # Admin can filter by any user_id
  59. query += " AND user_id = ?"
  60. params.append(user_id)
  61. query += " ORDER BY created_at DESC"
  62. cursor.execute(query, params)
  63. rows = cursor.fetchall()
  64. annotations = []
  65. for row in rows:
  66. # Parse JSON result
  67. result = json.loads(row["result"]) if isinstance(row["result"], str) else row["result"]
  68. annotations.append(AnnotationResponse(
  69. id=row["id"],
  70. task_id=row["task_id"],
  71. user_id=row["user_id"],
  72. result=result,
  73. created_at=row["created_at"],
  74. updated_at=row["updated_at"]
  75. ))
  76. return annotations
  77. @router.post("", response_model=AnnotationResponse, status_code=status.HTTP_201_CREATED)
  78. async def create_annotation(request: Request, annotation: AnnotationCreate):
  79. """
  80. Create a new annotation.
  81. Args:
  82. request: FastAPI Request object (contains user info)
  83. annotation: Annotation creation data
  84. Returns:
  85. Created annotation with generated ID
  86. Raises:
  87. HTTPException: 404 if task not found
  88. Requires authentication.
  89. Note: The annotation will be created with the current user's ID, ignoring any user_id in the request.
  90. """
  91. # Generate unique ID
  92. annotation_id = f"ann_{uuid.uuid4().hex[:12]}"
  93. # Get current user - always use authenticated user's ID
  94. user = request.state.user
  95. user_id = user["id"]
  96. with get_db_connection() as conn:
  97. cursor = conn.cursor()
  98. # Verify task exists
  99. cursor.execute("SELECT id FROM tasks WHERE id = ?", (annotation.task_id,))
  100. if not cursor.fetchone():
  101. raise HTTPException(
  102. status_code=status.HTTP_404_NOT_FOUND,
  103. detail=f"Task with id '{annotation.task_id}' not found"
  104. )
  105. # Serialize result to JSON
  106. result_json = json.dumps(annotation.result)
  107. # Insert new annotation with authenticated user's ID
  108. cursor.execute("""
  109. INSERT INTO annotations (id, task_id, user_id, result)
  110. VALUES (?, ?, ?, ?)
  111. """, (
  112. annotation_id,
  113. annotation.task_id,
  114. user_id, # Use authenticated user's ID
  115. result_json
  116. ))
  117. # Fetch the created annotation
  118. cursor.execute("""
  119. SELECT id, task_id, user_id, result, created_at, updated_at
  120. FROM annotations
  121. WHERE id = ?
  122. """, (annotation_id,))
  123. row = cursor.fetchone()
  124. # Parse JSON result
  125. result = json.loads(row["result"]) if isinstance(row["result"], str) else row["result"]
  126. return AnnotationResponse(
  127. id=row["id"],
  128. task_id=row["task_id"],
  129. user_id=row["user_id"],
  130. result=result,
  131. created_at=row["created_at"],
  132. updated_at=row["updated_at"]
  133. )
  134. @router.get("/{annotation_id}", response_model=AnnotationResponse)
  135. async def get_annotation(request: Request, annotation_id: str):
  136. """
  137. Get annotation by ID.
  138. Args:
  139. request: FastAPI Request object (contains user info)
  140. annotation_id: Annotation unique identifier
  141. Returns:
  142. Annotation details
  143. Raises:
  144. HTTPException: 404 if annotation not found
  145. HTTPException: 403 if user doesn't have permission to view this annotation
  146. Requires authentication.
  147. Note: Non-admin users can only view their own annotations.
  148. """
  149. # Get current user
  150. user = request.state.user
  151. with get_db_connection() as conn:
  152. cursor = conn.cursor()
  153. # Get annotation
  154. cursor.execute("""
  155. SELECT id, task_id, user_id, result, created_at, updated_at
  156. FROM annotations
  157. WHERE id = ?
  158. """, (annotation_id,))
  159. row = cursor.fetchone()
  160. if not row:
  161. raise HTTPException(
  162. status_code=status.HTTP_404_NOT_FOUND,
  163. detail=f"Annotation with id '{annotation_id}' not found"
  164. )
  165. # Check permission: non-admin users can only view their own annotations
  166. if user["role"] != "admin" and row["user_id"] != user["id"]:
  167. raise HTTPException(
  168. status_code=status.HTTP_403_FORBIDDEN,
  169. detail="您只能查看自己的标注"
  170. )
  171. # Parse JSON result
  172. result = json.loads(row["result"]) if isinstance(row["result"], str) else row["result"]
  173. return AnnotationResponse(
  174. id=row["id"],
  175. task_id=row["task_id"],
  176. user_id=row["user_id"],
  177. result=result,
  178. created_at=row["created_at"],
  179. updated_at=row["updated_at"]
  180. )
  181. @router.put("/{annotation_id}", response_model=AnnotationResponse)
  182. async def update_annotation(request: Request, annotation_id: str, annotation: AnnotationUpdate):
  183. """
  184. Update an existing annotation.
  185. Args:
  186. request: FastAPI Request object (contains user info)
  187. annotation_id: Annotation unique identifier
  188. annotation: Annotation update data
  189. Returns:
  190. Updated annotation details
  191. Raises:
  192. HTTPException: 404 if annotation not found
  193. HTTPException: 403 if user doesn't have permission to update this annotation
  194. Requires authentication.
  195. Note: Non-admin users can only update their own annotations.
  196. """
  197. # Get current user
  198. user = request.state.user
  199. with get_db_connection() as conn:
  200. cursor = conn.cursor()
  201. # Check if annotation exists and get owner
  202. cursor.execute("SELECT user_id FROM annotations WHERE id = ?", (annotation_id,))
  203. row = cursor.fetchone()
  204. if not row:
  205. raise HTTPException(
  206. status_code=status.HTTP_404_NOT_FOUND,
  207. detail=f"Annotation with id '{annotation_id}' not found"
  208. )
  209. # Check permission: non-admin users can only update their own annotations
  210. if user["role"] != "admin" and row["user_id"] != user["id"]:
  211. raise HTTPException(
  212. status_code=status.HTTP_403_FORBIDDEN,
  213. detail="您只能修改自己的标注"
  214. )
  215. # Serialize result to JSON
  216. result_json = json.dumps(annotation.result)
  217. # Update annotation
  218. cursor.execute("""
  219. UPDATE annotations
  220. SET result = ?, updated_at = CURRENT_TIMESTAMP
  221. WHERE id = ?
  222. """, (result_json, annotation_id))
  223. # Fetch and return updated annotation
  224. cursor.execute("""
  225. SELECT id, task_id, user_id, result, created_at, updated_at
  226. FROM annotations
  227. WHERE id = ?
  228. """, (annotation_id,))
  229. row = cursor.fetchone()
  230. # Parse JSON result
  231. result = json.loads(row["result"]) if isinstance(row["result"], str) else row["result"]
  232. return AnnotationResponse(
  233. id=row["id"],
  234. task_id=row["task_id"],
  235. user_id=row["user_id"],
  236. result=result,
  237. created_at=row["created_at"],
  238. updated_at=row["updated_at"]
  239. )
  240. @router.get("/tasks/{task_id}/annotations", response_model=List[AnnotationResponse])
  241. async def get_task_annotations(request: Request, task_id: str):
  242. """
  243. Get all annotations for a specific task.
  244. Args:
  245. request: FastAPI Request object (contains user info)
  246. task_id: Task unique identifier
  247. Returns:
  248. List of annotations belonging to the task
  249. Raises:
  250. HTTPException: 404 if task not found
  251. Requires authentication.
  252. """
  253. with get_db_connection() as conn:
  254. cursor = conn.cursor()
  255. # Verify task exists
  256. cursor.execute("SELECT id FROM tasks WHERE id = ?", (task_id,))
  257. if not cursor.fetchone():
  258. raise HTTPException(
  259. status_code=status.HTTP_404_NOT_FOUND,
  260. detail=f"Task with id '{task_id}' not found"
  261. )
  262. # Get all annotations for the task
  263. cursor.execute("""
  264. SELECT id, task_id, user_id, result, created_at, updated_at
  265. FROM annotations
  266. WHERE task_id = ?
  267. ORDER BY created_at DESC
  268. """, (task_id,))
  269. rows = cursor.fetchall()
  270. annotations = []
  271. for row in rows:
  272. # Parse JSON result
  273. result = json.loads(row["result"]) if isinstance(row["result"], str) else row["result"]
  274. annotations.append(AnnotationResponse(
  275. id=row["id"],
  276. task_id=row["task_id"],
  277. user_id=row["user_id"],
  278. result=result,
  279. created_at=row["created_at"],
  280. updated_at=row["updated_at"]
  281. ))
  282. return annotations