Files
SDI 12e4f17b62
CI / typecheck (push) Successful in 1m8s
CI / format (push) Failing after 1m6s
CI / lint (push) Failing after 49s
CI / test (push) Failing after 1m8s
first commit
2026-07-15 18:05:12 +09:00

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)