"""Task personali del prodotto pubblico. Tutte le query applicano owner_id derivato dal JWT Supabase verificato. Il client non può scegliere o sostituire il proprietario nel body o nella query. """ from __future__ import annotations import asyncio import logging from typing import Any from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field from .auth_guard import require_supabase_user from .state import sb _logger = logging.getLogger("agente_ai.api.me_tasks") router = APIRouter(prefix="/api/me/tasks", tags=["me"]) class TaskCreate(BaseModel): goal: str = Field(min_length=1, max_length=10_000) class TaskUpdate(BaseModel): status: str = Field(pattern="^(queued|in_progress|done|failed|cancelled)$") _ALLOWED = "id,goal,status,created_at,updated_at" def _owner(user: dict[str, Any]) -> str: return str(user["id"]) def _client(): client = sb() if client is None: raise HTTPException(status_code=503, detail="Database non configurato") return client @router.get("") async def list_my_tasks( user: dict[str, Any] = Depends(require_supabase_user), limit: int = Query(50, ge=1, le=100), offset: int = Query(0, ge=0), ) -> dict[str, Any]: client = _client() owner_id = _owner(user) def operation(): return client.table("user_agent_tasks").select(_ALLOWED).eq("owner_id", owner_id).order("updated_at", desc=True).range(offset, offset + limit - 1).execute() try: result = await asyncio.to_thread(operation) return {"tasks": result.data or [], "offset": offset, "limit": limit} except Exception as exc: _logger.warning("list own tasks failed: %s", type(exc).__name__) raise HTTPException(status_code=503, detail="Task personali temporaneamente non disponibili") from exc @router.post("", status_code=201) async def create_my_task( body: TaskCreate, user: dict[str, Any] = Depends(require_supabase_user), ) -> dict[str, Any]: client = _client() owner_id = _owner(user) def operation(): return client.table("user_agent_tasks").insert({"owner_id": owner_id, "goal": body.goal.strip(), "status": "queued"}).select(_ALLOWED).single().execute() try: result = await asyncio.to_thread(operation) if not result.data: raise HTTPException(status_code=502, detail="Task personale non creato") return result.data except HTTPException: raise except Exception as exc: _logger.warning("create own task failed: %s", type(exc).__name__) raise HTTPException(status_code=503, detail="Task personale temporaneamente non disponibile") from exc @router.patch("/{task_id}") async def update_my_task( task_id: UUID, body: TaskUpdate, user: dict[str, Any] = Depends(require_supabase_user), ) -> dict[str, Any]: client = _client() owner_id = _owner(user) def operation(): return client.table("user_agent_tasks").update({"status": body.status}).eq("id", str(task_id)).eq("owner_id", owner_id).select(_ALLOWED).maybe_single().execute() try: result = await asyncio.to_thread(operation) if not result.data: raise HTTPException(status_code=404, detail="Task personale non trovato") return result.data except HTTPException: raise except Exception as exc: _logger.warning("update own task failed: %s", type(exc).__name__) raise HTTPException(status_code=503, detail="Task personale temporaneamente non disponibile") from exc @router.post("/{task_id}/cancel") async def cancel_my_task( task_id: UUID, user: dict[str, Any] = Depends(require_supabase_user), ) -> dict[str, Any]: return await update_my_task(task_id, TaskUpdate(status="cancelled"), user)