feat(incidents): add incident lifecycle api and tests
This commit is contained in:
103
app/api/v1/incidents.py
Normal file
103
app/api/v1/incidents.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""Incident API endpoints."""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
|
||||
from app.api.deps import CurrentUser, get_current_user, require_role
|
||||
from app.schemas.common import PaginatedResponse
|
||||
from app.schemas.incident import (
|
||||
CommentRequest,
|
||||
IncidentEventResponse,
|
||||
IncidentResponse,
|
||||
IncidentStatus,
|
||||
TransitionRequest,
|
||||
IncidentCreate,
|
||||
)
|
||||
from app.services import IncidentService
|
||||
|
||||
|
||||
router = APIRouter(tags=["incidents"])
|
||||
incident_service = IncidentService()
|
||||
|
||||
|
||||
@router.get("/incidents", response_model=PaginatedResponse[IncidentResponse])
|
||||
async def list_incidents(
|
||||
status: IncidentStatus | None = Query(default=None),
|
||||
cursor: datetime | None = Query(default=None, description="Cursor (created_at)"),
|
||||
limit: int = Query(default=20, ge=1, le=100),
|
||||
current_user: CurrentUser = Depends(get_current_user),
|
||||
) -> PaginatedResponse[IncidentResponse]:
|
||||
"""List incidents for the active organization."""
|
||||
|
||||
return await incident_service.get_incidents(
|
||||
current_user,
|
||||
status=status,
|
||||
cursor=cursor,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/services/{service_id}/incidents",
|
||||
response_model=IncidentResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_incident(
|
||||
service_id: UUID,
|
||||
payload: IncidentCreate,
|
||||
current_user: CurrentUser = Depends(require_role("member")),
|
||||
) -> IncidentResponse:
|
||||
"""Create a new incident for the given service (member+)."""
|
||||
|
||||
return await incident_service.create_incident(current_user, service_id, payload)
|
||||
|
||||
|
||||
@router.get("/incidents/{incident_id}", response_model=IncidentResponse)
|
||||
async def get_incident(
|
||||
incident_id: UUID,
|
||||
current_user: CurrentUser = Depends(get_current_user),
|
||||
) -> IncidentResponse:
|
||||
"""Fetch a single incident by ID."""
|
||||
|
||||
return await incident_service.get_incident(current_user, incident_id)
|
||||
|
||||
|
||||
@router.get("/incidents/{incident_id}/events", response_model=list[IncidentEventResponse])
|
||||
async def get_incident_events(
|
||||
incident_id: UUID,
|
||||
current_user: CurrentUser = Depends(get_current_user),
|
||||
) -> list[IncidentEventResponse]:
|
||||
"""Get the event timeline for an incident."""
|
||||
|
||||
return await incident_service.get_incident_events(current_user, incident_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/incidents/{incident_id}/transition",
|
||||
response_model=IncidentResponse,
|
||||
)
|
||||
async def transition_incident(
|
||||
incident_id: UUID,
|
||||
payload: TransitionRequest,
|
||||
current_user: CurrentUser = Depends(require_role("member")),
|
||||
) -> IncidentResponse:
|
||||
"""Transition an incident status (member+)."""
|
||||
|
||||
return await incident_service.transition_incident(current_user, incident_id, payload)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/incidents/{incident_id}/comment",
|
||||
response_model=IncidentEventResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def add_comment(
|
||||
incident_id: UUID,
|
||||
payload: CommentRequest,
|
||||
current_user: CurrentUser = Depends(require_role("member")),
|
||||
) -> IncidentEventResponse:
|
||||
"""Add a comment to the incident timeline (member+)."""
|
||||
|
||||
return await incident_service.add_comment(current_user, incident_id, payload)
|
||||
72
app/api/v1/org.py
Normal file
72
app/api/v1/org.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Organization API endpoints."""
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
|
||||
from app.api.deps import CurrentUser, get_current_user, require_role
|
||||
from app.schemas.org import (
|
||||
MemberResponse,
|
||||
NotificationTargetCreate,
|
||||
NotificationTargetResponse,
|
||||
OrgResponse,
|
||||
ServiceCreate,
|
||||
ServiceResponse,
|
||||
)
|
||||
from app.services import OrgService
|
||||
|
||||
|
||||
router = APIRouter(prefix="/org", tags=["org"])
|
||||
org_service = OrgService()
|
||||
|
||||
|
||||
@router.get("", response_model=OrgResponse)
|
||||
async def get_org(current_user: CurrentUser = Depends(get_current_user)) -> OrgResponse:
|
||||
"""Return the active organization summary for the authenticated user."""
|
||||
|
||||
return await org_service.get_current_org(current_user)
|
||||
|
||||
|
||||
@router.get("/members", response_model=list[MemberResponse])
|
||||
async def list_members(current_user: CurrentUser = Depends(require_role("admin"))) -> list[MemberResponse]:
|
||||
"""List members of the current organization (admin only)."""
|
||||
|
||||
return await org_service.get_members(current_user)
|
||||
|
||||
|
||||
@router.get("/services", response_model=list[ServiceResponse])
|
||||
async def list_services(current_user: CurrentUser = Depends(get_current_user)) -> list[ServiceResponse]:
|
||||
"""List services for the current organization."""
|
||||
|
||||
return await org_service.get_services(current_user)
|
||||
|
||||
|
||||
@router.post("/services", response_model=ServiceResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_service(
|
||||
payload: ServiceCreate,
|
||||
current_user: CurrentUser = Depends(require_role("member")),
|
||||
) -> ServiceResponse:
|
||||
"""Create a new service within the current organization (member+)."""
|
||||
|
||||
return await org_service.create_service(current_user, payload)
|
||||
|
||||
|
||||
@router.get("/notification-targets", response_model=list[NotificationTargetResponse])
|
||||
async def list_notification_targets(
|
||||
current_user: CurrentUser = Depends(require_role("admin")),
|
||||
) -> list[NotificationTargetResponse]:
|
||||
"""List notification targets for the current organization (admin only)."""
|
||||
|
||||
return await org_service.get_notification_targets(current_user)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/notification-targets",
|
||||
response_model=NotificationTargetResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_notification_target(
|
||||
payload: NotificationTargetCreate,
|
||||
current_user: CurrentUser = Depends(require_role("admin")),
|
||||
) -> NotificationTargetResponse:
|
||||
"""Create a notification target for the current organization (admin only)."""
|
||||
|
||||
return await org_service.create_notification_target(current_user, payload)
|
||||
Reference in New Issue
Block a user