feat(incidents): add incident lifecycle api and tests
This commit is contained in:
115
app/services/org.py
Normal file
115
app/services/org.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""Organization service providing org-scoped operations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import asyncpg
|
||||
from asyncpg.pool import PoolConnectionProxy
|
||||
|
||||
from app.api.deps import CurrentUser
|
||||
from app.core import exceptions as exc
|
||||
from app.db import Database, db
|
||||
from app.repositories import NotificationRepository, OrgRepository, ServiceRepository
|
||||
from app.schemas.org import (
|
||||
MemberResponse,
|
||||
NotificationTargetCreate,
|
||||
NotificationTargetResponse,
|
||||
OrgResponse,
|
||||
ServiceCreate,
|
||||
ServiceResponse,
|
||||
)
|
||||
|
||||
|
||||
def _as_conn(conn: asyncpg.Connection | PoolConnectionProxy) -> asyncpg.Connection:
|
||||
"""Helper to satisfy typing when a pool proxy is returned."""
|
||||
|
||||
return cast(asyncpg.Connection, conn)
|
||||
|
||||
|
||||
class OrgService:
|
||||
"""Encapsulates organization-level operations within the active org context."""
|
||||
|
||||
def __init__(self, database: Database | None = None) -> None:
|
||||
self.db = database or db
|
||||
|
||||
async def get_current_org(self, current_user: CurrentUser) -> OrgResponse:
|
||||
"""Return the active organization summary for the current user."""
|
||||
|
||||
async with self.db.connection() as conn:
|
||||
org_repo = OrgRepository(_as_conn(conn))
|
||||
org = await org_repo.get_by_id(current_user.org_id)
|
||||
if org is None:
|
||||
raise exc.NotFoundError("Organization not found")
|
||||
return OrgResponse(**org)
|
||||
|
||||
async def get_members(self, current_user: CurrentUser) -> list[MemberResponse]:
|
||||
"""List members of the active organization."""
|
||||
|
||||
async with self.db.connection() as conn:
|
||||
org_repo = OrgRepository(_as_conn(conn))
|
||||
members = await org_repo.get_members(current_user.org_id)
|
||||
return [MemberResponse(**member) for member in members]
|
||||
|
||||
async def create_service(self, current_user: CurrentUser, data: ServiceCreate) -> ServiceResponse:
|
||||
"""Create a new service within the active organization."""
|
||||
|
||||
async with self.db.connection() as conn:
|
||||
service_repo = ServiceRepository(_as_conn(conn))
|
||||
|
||||
if await service_repo.slug_exists(current_user.org_id, data.slug):
|
||||
raise exc.ConflictError("Service slug already exists in this organization")
|
||||
|
||||
try:
|
||||
service = await service_repo.create(
|
||||
service_id=uuid4(),
|
||||
org_id=current_user.org_id,
|
||||
name=data.name,
|
||||
slug=data.slug,
|
||||
)
|
||||
except asyncpg.UniqueViolationError as err: # pragma: no cover - race protection
|
||||
raise exc.ConflictError("Service slug already exists in this organization") from err
|
||||
|
||||
return ServiceResponse(**service)
|
||||
|
||||
async def get_services(self, current_user: CurrentUser) -> list[ServiceResponse]:
|
||||
"""List services for the active organization."""
|
||||
|
||||
async with self.db.connection() as conn:
|
||||
service_repo = ServiceRepository(_as_conn(conn))
|
||||
services = await service_repo.get_by_org(current_user.org_id)
|
||||
return [ServiceResponse(**svc) for svc in services]
|
||||
|
||||
async def create_notification_target(
|
||||
self,
|
||||
current_user: CurrentUser,
|
||||
data: NotificationTargetCreate,
|
||||
) -> NotificationTargetResponse:
|
||||
"""Create a notification target for the active organization."""
|
||||
|
||||
if data.target_type == "webhook" and data.webhook_url is None:
|
||||
raise exc.BadRequestError("webhook_url is required for webhook targets")
|
||||
|
||||
async with self.db.connection() as conn:
|
||||
notification_repo = NotificationRepository(_as_conn(conn))
|
||||
target = await notification_repo.create_target(
|
||||
target_id=uuid4(),
|
||||
org_id=current_user.org_id,
|
||||
name=data.name,
|
||||
target_type=data.target_type,
|
||||
webhook_url=str(data.webhook_url) if data.webhook_url else None,
|
||||
enabled=data.enabled,
|
||||
)
|
||||
return NotificationTargetResponse(**target)
|
||||
|
||||
async def get_notification_targets(self, current_user: CurrentUser) -> list[NotificationTargetResponse]:
|
||||
"""List notification targets for the active organization."""
|
||||
|
||||
async with self.db.connection() as conn:
|
||||
notification_repo = NotificationRepository(_as_conn(conn))
|
||||
targets = await notification_repo.get_targets_by_org(current_user.org_id)
|
||||
return [NotificationTargetResponse(**target) for target in targets]
|
||||
|
||||
|
||||
__all__ = ["OrgService"]
|
||||
Reference in New Issue
Block a user