"""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"]