188 lines
6.6 KiB
Python
188 lines
6.6 KiB
Python
import json
|
|
from typing import cast
|
|
|
|
from fastapi import APIRouter, Depends, Request, UploadFile
|
|
from fastapi.responses import FileResponse
|
|
from sqlalchemy.orm import Session
|
|
from starlette.datastructures import UploadFile as StarletteUploadFile
|
|
|
|
from app.db.session import get_db
|
|
from app.schemas.ticket import (
|
|
SupportTicketCommentRecord,
|
|
SupportTicketRecord,
|
|
TicketCommentCreateRequest,
|
|
TicketIssueLinkStatusSyncRequest,
|
|
TicketCommentUpdateRequest,
|
|
TicketCreateRequest,
|
|
TicketCreateResponse,
|
|
TicketMutationResponse,
|
|
WorkspaceFormTemplateResponse,
|
|
WorkspaceSummary,
|
|
)
|
|
from app.services.ticket_service import TicketService
|
|
|
|
router = APIRouter(prefix="/workspaces", tags=["tickets"])
|
|
service = TicketService()
|
|
|
|
|
|
@router.get("", response_model=list[WorkspaceSummary])
|
|
def list_workspaces(db: Session = Depends(get_db)) -> list[WorkspaceSummary]:
|
|
return service.list_workspaces(db)
|
|
|
|
|
|
@router.get("/{workspace_code}/form-template", response_model=WorkspaceFormTemplateResponse)
|
|
def get_workspace_form_template(
|
|
workspace_code: str,
|
|
db: Session = Depends(get_db),
|
|
) -> WorkspaceFormTemplateResponse:
|
|
return service.get_workspace_form_template(db, workspace_code)
|
|
|
|
|
|
@router.post("/{workspace_code}/tickets", response_model=TicketCreateResponse)
|
|
async def create_ticket(
|
|
workspace_code: str,
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
) -> TicketCreateResponse:
|
|
content_type = request.headers.get("content-type", "")
|
|
|
|
if content_type.startswith("multipart/form-data"):
|
|
form = await request.form()
|
|
extra_fields_value = form.get("extra_fields")
|
|
extra_fields = (
|
|
json.loads(extra_fields_value)
|
|
if isinstance(extra_fields_value, str) and extra_fields_value
|
|
else {}
|
|
)
|
|
attachments = [
|
|
cast(UploadFile, value)
|
|
for key, value in form.multi_items()
|
|
if key == "attachments"
|
|
and isinstance(value, (UploadFile, StarletteUploadFile))
|
|
]
|
|
request_payload = TicketCreateRequest(
|
|
workspace_code=workspace_code,
|
|
requester_id=str(form.get("requester_id") or ""),
|
|
requester_tenant_id=str(form.get("requester_tenant_id") or ""),
|
|
requester_contact=str(form.get("requester_contact") or ""),
|
|
title=str(form.get("title") or ""),
|
|
description=str(form.get("description") or ""),
|
|
category_code=str(form.get("category_code") or ""),
|
|
ticket_type=str(form.get("ticket_type") or "GENERAL"),
|
|
requires_approval=str(form.get("requires_approval") or "false").lower() == "true",
|
|
extra_fields=extra_fields,
|
|
)
|
|
return service.create_ticket(db, request_payload, attachments)
|
|
|
|
payload = TicketCreateRequest.model_validate(await request.json())
|
|
request_payload = payload.model_copy(update={"workspace_code": workspace_code})
|
|
return service.create_ticket(db, request_payload)
|
|
|
|
|
|
@router.get("/{workspace_code}/tickets", response_model=list[SupportTicketRecord])
|
|
def list_workspace_tickets(
|
|
workspace_code: str,
|
|
requesterId: str | None = None,
|
|
requesterTenantId: str | None = None,
|
|
db: Session = Depends(get_db),
|
|
) -> list[SupportTicketRecord]:
|
|
return service.list_workspace_tickets(db, workspace_code, requesterId, requesterTenantId)
|
|
|
|
|
|
@router.get("/../tickets", include_in_schema=False)
|
|
def _noop_redirect() -> None:
|
|
return None
|
|
|
|
|
|
ticket_router = APIRouter(prefix="/tickets", tags=["support-tickets"])
|
|
|
|
|
|
@ticket_router.get("", response_model=list[SupportTicketRecord])
|
|
def list_tickets(db: Session = Depends(get_db)) -> list[SupportTicketRecord]:
|
|
return service.list_tickets(db)
|
|
|
|
|
|
@ticket_router.get("/{ticket_id}", response_model=SupportTicketRecord)
|
|
def get_ticket(
|
|
ticket_id: int,
|
|
workspaceCode: str | None = None,
|
|
db: Session = Depends(get_db),
|
|
) -> SupportTicketRecord:
|
|
return service.get_ticket(db, ticket_id, workspaceCode)
|
|
|
|
|
|
@ticket_router.get("/{ticket_id}/attachments/{attachment_id}")
|
|
def download_ticket_attachment(
|
|
ticket_id: int,
|
|
attachment_id: int,
|
|
workspaceCode: str | None = None,
|
|
db: Session = Depends(get_db),
|
|
) -> FileResponse:
|
|
attachment = service.get_ticket_attachment(db, ticket_id, attachment_id, workspaceCode)
|
|
file_path = service.resolve_attachment_path(attachment)
|
|
return FileResponse(
|
|
path=file_path,
|
|
media_type=attachment.mime_type or "application/octet-stream",
|
|
filename=attachment.original_file_name,
|
|
)
|
|
|
|
|
|
@ticket_router.get("/{ticket_id}/comments", response_model=list[SupportTicketCommentRecord])
|
|
def list_ticket_comments(
|
|
ticket_id: int,
|
|
workspaceCode: str | None = None,
|
|
db: Session = Depends(get_db),
|
|
) -> list[SupportTicketCommentRecord]:
|
|
return service.list_ticket_comments(db, ticket_id, workspaceCode)
|
|
|
|
|
|
@ticket_router.post("/{ticket_id}/comments", response_model=SupportTicketCommentRecord)
|
|
def create_ticket_comment(
|
|
ticket_id: int,
|
|
payload: TicketCommentCreateRequest,
|
|
workspaceCode: str | None = None,
|
|
db: Session = Depends(get_db),
|
|
) -> SupportTicketCommentRecord:
|
|
return service.create_ticket_comment(db, ticket_id, payload, workspaceCode)
|
|
|
|
|
|
@ticket_router.put("/{ticket_id}/comments/{comment_id}", response_model=SupportTicketCommentRecord)
|
|
def update_ticket_comment(
|
|
ticket_id: int,
|
|
comment_id: int,
|
|
payload: TicketCommentUpdateRequest,
|
|
db: Session = Depends(get_db),
|
|
) -> SupportTicketCommentRecord:
|
|
return service.update_ticket_comment(db, ticket_id, comment_id, payload)
|
|
|
|
|
|
@ticket_router.delete("/{ticket_id}/comments/{comment_id}")
|
|
def delete_ticket_comment(
|
|
ticket_id: int,
|
|
comment_id: int,
|
|
requesterId: str,
|
|
requesterTenantId: str,
|
|
db: Session = Depends(get_db),
|
|
) -> dict[str, int | bool]:
|
|
return service.delete_ticket_comment(db, ticket_id, comment_id, requesterId, requesterTenantId)
|
|
|
|
|
|
@ticket_router.post("/{ticket_id}/approve", response_model=TicketMutationResponse)
|
|
def approve_ticket(ticket_id: int, db: Session = Depends(get_db)) -> TicketMutationResponse:
|
|
return service.approve_ticket(db, ticket_id)
|
|
|
|
|
|
@ticket_router.post("/{ticket_id}/issue", response_model=TicketMutationResponse)
|
|
def create_issue(ticket_id: int, db: Session = Depends(get_db)) -> TicketMutationResponse:
|
|
return service.create_issue(db, ticket_id)
|
|
|
|
|
|
@ticket_router.post("/{ticket_id}/issue-link-status")
|
|
def sync_issue_link_status(
|
|
ticket_id: int,
|
|
payload: TicketIssueLinkStatusSyncRequest,
|
|
workspaceCode: str | None = None,
|
|
db: Session = Depends(get_db),
|
|
) -> dict[str, int | str | bool]:
|
|
return service.sync_issue_link_status(db, ticket_id, payload.linked, workspaceCode)
|