116 lines
4.5 KiB
Python
116 lines
4.5 KiB
Python
"""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"]
|