forked from cardosofelipe/pragma-stack
- Consistently format multi-line function headers, exception handling, and repository method calls for readability. - Reorganize misplaced imports across modules (e.g., services & tests) into proper sorted order. - Adjust indentation, line breaks, and spacing inconsistencies in tests and migration files. - Cleanup unnecessary trailing newlines and reorganize `__all__` declarations for consistency.
156 lines
5.3 KiB
Python
156 lines
5.3 KiB
Python
# app/services/organization_service.py
|
|
"""Service layer for organization operations — delegates to OrganizationRepository."""
|
|
|
|
import logging
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.exceptions import NotFoundError
|
|
from app.models.organization import Organization
|
|
from app.models.user_organization import OrganizationRole, UserOrganization
|
|
from app.repositories.organization import OrganizationRepository, organization_repo
|
|
from app.schemas.organizations import OrganizationCreate, OrganizationUpdate
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class OrganizationService:
|
|
"""Service for organization management operations."""
|
|
|
|
def __init__(
|
|
self, organization_repository: OrganizationRepository | None = None
|
|
) -> None:
|
|
self._repo = organization_repository or organization_repo
|
|
|
|
async def get_organization(self, db: AsyncSession, org_id: str) -> Organization:
|
|
"""Get organization by ID, raising NotFoundError if not found."""
|
|
org = await self._repo.get(db, id=org_id)
|
|
if not org:
|
|
raise NotFoundError(f"Organization {org_id} not found")
|
|
return org
|
|
|
|
async def create_organization(
|
|
self, db: AsyncSession, *, obj_in: OrganizationCreate
|
|
) -> Organization:
|
|
"""Create a new organization."""
|
|
return await self._repo.create(db, obj_in=obj_in)
|
|
|
|
async def update_organization(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
org: Organization,
|
|
obj_in: OrganizationUpdate | dict[str, Any],
|
|
) -> Organization:
|
|
"""Update an existing organization."""
|
|
return await self._repo.update(db, db_obj=org, obj_in=obj_in)
|
|
|
|
async def remove_organization(self, db: AsyncSession, org_id: str) -> None:
|
|
"""Permanently delete an organization by ID."""
|
|
await self._repo.remove(db, id=org_id)
|
|
|
|
async def get_member_count(self, db: AsyncSession, *, organization_id: UUID) -> int:
|
|
"""Get number of active members in an organization."""
|
|
return await self._repo.get_member_count(db, organization_id=organization_id)
|
|
|
|
async def get_multi_with_member_counts(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
is_active: bool | None = None,
|
|
search: str | None = None,
|
|
) -> tuple[list[dict[str, Any]], int]:
|
|
"""List organizations with member counts and pagination."""
|
|
return await self._repo.get_multi_with_member_counts(
|
|
db, skip=skip, limit=limit, is_active=is_active, search=search
|
|
)
|
|
|
|
async def get_user_organizations_with_details(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
user_id: UUID,
|
|
is_active: bool | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
"""Get all organizations a user belongs to, with membership details."""
|
|
return await self._repo.get_user_organizations_with_details(
|
|
db, user_id=user_id, is_active=is_active
|
|
)
|
|
|
|
async def get_organization_members(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
organization_id: UUID,
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
is_active: bool | None = True,
|
|
) -> tuple[list[dict[str, Any]], int]:
|
|
"""Get members of an organization with pagination."""
|
|
return await self._repo.get_organization_members(
|
|
db,
|
|
organization_id=organization_id,
|
|
skip=skip,
|
|
limit=limit,
|
|
is_active=is_active,
|
|
)
|
|
|
|
async def add_member(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
organization_id: UUID,
|
|
user_id: UUID,
|
|
role: OrganizationRole = OrganizationRole.MEMBER,
|
|
) -> UserOrganization:
|
|
"""Add a user to an organization."""
|
|
return await self._repo.add_user(
|
|
db, organization_id=organization_id, user_id=user_id, role=role
|
|
)
|
|
|
|
async def remove_member(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
organization_id: UUID,
|
|
user_id: UUID,
|
|
) -> bool:
|
|
"""Remove a user from an organization. Returns True if found and removed."""
|
|
return await self._repo.remove_user(
|
|
db, organization_id=organization_id, user_id=user_id
|
|
)
|
|
|
|
async def get_user_role_in_org(
|
|
self, db: AsyncSession, *, user_id: UUID, organization_id: UUID
|
|
) -> OrganizationRole | None:
|
|
"""Get the role of a user in an organization."""
|
|
return await self._repo.get_user_role_in_org(
|
|
db, user_id=user_id, organization_id=organization_id
|
|
)
|
|
|
|
async def get_org_distribution(
|
|
self, db: AsyncSession, *, limit: int = 6
|
|
) -> list[dict[str, Any]]:
|
|
"""Return top organizations by member count for admin dashboard."""
|
|
from sqlalchemy import func, select
|
|
|
|
result = await db.execute(
|
|
select(
|
|
Organization.name,
|
|
func.count(UserOrganization.user_id).label("count"),
|
|
)
|
|
.join(UserOrganization, Organization.id == UserOrganization.organization_id)
|
|
.group_by(Organization.name)
|
|
.order_by(func.count(UserOrganization.user_id).desc())
|
|
.limit(limit)
|
|
)
|
|
return [{"name": row.name, "value": row.count} for row in result.all()]
|
|
|
|
|
|
# Default singleton
|
|
organization_service = OrganizationService()
|