first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""API routing package."""
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.routes.health import router as health_router
|
||||
from app.api.routes.tickets import router as workspace_ticket_router, ticket_router
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(health_router, prefix="/api")
|
||||
api_router.include_router(workspace_ticket_router, prefix="/api")
|
||||
api_router.include_router(ticket_router, prefix="/api")
|
||||
@@ -0,0 +1 @@
|
||||
"""API route modules."""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def health_check() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
@@ -0,0 +1,187 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user