"""Tasks plugin routes — CRUD, assign, status update.""" from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.deps import get_current_user, require_permission from app.plugins.builtins.tasks import services from app.plugins.builtins.tasks.schemas import ( TaskAssignRequest, TaskCreate, TaskStatusRequest, TaskUpdate, ) router = APIRouter(prefix="/api/v1/tasks", tags=["tasks"]) def _parse_uuid(val: str, field: str) -> uuid.UUID: try: return uuid.UUID(val) except (ValueError, TypeError): raise HTTPException( 400, detail={"detail": f"Invalid {field}", "code": "invalid_id"} ) from None @router.get("", dependencies=[Depends(require_permission("tasks:read"))]) async def list_tasks( page: int = Query(1, ge=1), page_size: int = Query(25, ge=1, le=100), status: str | None = Query(None, pattern="^(open|in_progress|done)$"), priority: str | None = Query(None, pattern="^(low|medium|high|urgent)$"), assigned_to: str | None = Query(None), contact_id: str | None = Query(None), search: str | None = Query(None), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """List tasks with filtering and pagination.""" tenant_id = uuid.UUID(current_user["tenant_id"]) return await services.list_tasks( db, tenant_id, page=page, page_size=page_size, status=status, priority=priority, assigned_to=assigned_to, contact_id=contact_id, search=search, ) @router.post("", status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_permission("tasks:write"))]) async def create_task( body: TaskCreate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Create a new task.""" tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) data = body.model_dump() return await services.create_task(db, tenant_id, user_id, data) @router.get("/{task_id}", dependencies=[Depends(require_permission("tasks:read"))]) async def get_task( task_id: str, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Get a single task by ID.""" tenant_id = uuid.UUID(current_user["tenant_id"]) tid = _parse_uuid(task_id, "task_id") result = await services.get_task(db, tenant_id, tid) if result is None: raise HTTPException(404, detail={"detail": "Task not found", "code": "not_found"}) return result @router.patch("/{task_id}", dependencies=[Depends(require_permission("tasks:write"))]) async def update_task( task_id: str, body: TaskUpdate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Update a task.""" tenant_id = uuid.UUID(current_user["tenant_id"]) tid = _parse_uuid(task_id, "task_id") data = body.model_dump(exclude_unset=True) result = await services.update_task(db, tenant_id, tid, data) if result is None: raise HTTPException(404, detail={"detail": "Task not found", "code": "not_found"}) return result @router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(require_permission("tasks:delete"))]) async def delete_task( task_id: str, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Delete a task (soft-delete).""" tenant_id = uuid.UUID(current_user["tenant_id"]) tid = _parse_uuid(task_id, "task_id") deleted = await services.delete_task(db, tenant_id, tid) if not deleted: raise HTTPException(404, detail={"detail": "Task not found", "code": "not_found"}) return Response(status_code=status.HTTP_204_NO_CONTENT) @router.post("/{task_id}/assign", dependencies=[Depends(require_permission("tasks:write"))]) async def assign_task( task_id: str, body: TaskAssignRequest, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Assign a task to a user.""" tenant_id = uuid.UUID(current_user["tenant_id"]) tid = _parse_uuid(task_id, "task_id") assigned_to = _parse_uuid(body.assigned_to, "assigned_to") result = await services.assign_task(db, tenant_id, tid, assigned_to) if result is None: raise HTTPException(404, detail={"detail": "Task not found", "code": "not_found"}) return result @router.post("/{task_id}/status", dependencies=[Depends(require_permission("tasks:write"))]) async def update_task_status( task_id: str, body: TaskStatusRequest, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Update task status.""" tenant_id = uuid.UUID(current_user["tenant_id"]) tid = _parse_uuid(task_id, "task_id") result = await services.update_task_status(db, tenant_id, tid, body.status) if result is None: raise HTTPException(404, detail={"detail": "Task not found", "code": "not_found"}) return result