feat: add GET /integrations/psa/tickets/{id}/context endpoint (Task 9)
Returns rich TicketContext for a ticket ID. Handles PSA auth failures (returns structured error), ticket-not-found (404), and general PSA errors (502). Requires active PSA connection for the user's account. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -319,6 +319,61 @@ async def search_tickets(
|
|||||||
raise HTTPException(status_code=502, detail=str(e))
|
raise HTTPException(status_code=502, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tickets/{ticket_id}/context")
|
||||||
|
async def get_ticket_context(
|
||||||
|
ticket_id: int,
|
||||||
|
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db)],
|
||||||
|
):
|
||||||
|
"""Get rich ticket context (company, contact, configs, notes, related tickets) for AI prompt injection."""
|
||||||
|
from app.services.psa.registry import get_provider_for_account
|
||||||
|
from app.services.psa.exceptions import (
|
||||||
|
PSAError,
|
||||||
|
PSAAuthError,
|
||||||
|
PSAPermissionError,
|
||||||
|
PSANotFoundError,
|
||||||
|
PSAConnectionError,
|
||||||
|
)
|
||||||
|
from app.schemas.psa_context import TicketContext
|
||||||
|
|
||||||
|
if not current_user.account_id:
|
||||||
|
raise HTTPException(status_code=400, detail="User has no account")
|
||||||
|
|
||||||
|
# Look up the active connection for connection_id
|
||||||
|
conn_result = await db.execute(
|
||||||
|
select(PsaConnection).where(
|
||||||
|
PsaConnection.account_id == current_user.account_id,
|
||||||
|
PsaConnection.is_active.is_(True),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
connection = conn_result.scalar_one_or_none()
|
||||||
|
if not connection:
|
||||||
|
raise HTTPException(status_code=404, detail="No active PSA connection configured")
|
||||||
|
|
||||||
|
try:
|
||||||
|
provider = await get_provider_for_account(current_user.account_id, db)
|
||||||
|
except PSAConnectionError:
|
||||||
|
raise HTTPException(status_code=404, detail="No active PSA connection configured")
|
||||||
|
except PSAError as e:
|
||||||
|
raise HTTPException(status_code=502, detail=str(e))
|
||||||
|
|
||||||
|
try:
|
||||||
|
ctx: TicketContext = await provider.get_ticket_context(
|
||||||
|
ticket_id=ticket_id,
|
||||||
|
connection_id=str(connection.id),
|
||||||
|
)
|
||||||
|
return ctx
|
||||||
|
except (PSAAuthError, PSAPermissionError):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=502,
|
||||||
|
detail={"error": "psa_auth_failed", "message": "PSA credentials may have expired."},
|
||||||
|
)
|
||||||
|
except PSANotFoundError:
|
||||||
|
raise HTTPException(status_code=404, detail="Ticket not found")
|
||||||
|
except PSAError as e:
|
||||||
|
raise HTTPException(status_code=502, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/tickets/{ticket_id}")
|
@router.get("/tickets/{ticket_id}")
|
||||||
async def get_ticket(
|
async def get_ticket(
|
||||||
ticket_id: str,
|
ticket_id: str,
|
||||||
|
|||||||
Reference in New Issue
Block a user