""" Servizio utenti – CRUD utenti per admin del tenant. """ import math import uuid from sqlalchemy import func, select, update from sqlalchemy.ext.asyncio import AsyncSession from app.core.exceptions import ConflictError, ForbiddenError, NotFoundError, TenantLimitExceededError from app.core.security import hash_password from app.models.tenant import Tenant from app.models.user import User from app.schemas.user import UserCreateRequest, UserUpdateRequest class UserService: def __init__(self, db: AsyncSession) -> None: self.db = db async def create_user( self, tenant_id: uuid.UUID, data: UserCreateRequest, created_by: User, ) -> User: """Crea un nuovo utente nel tenant. Solo admin può farlo.""" # Verifica limite utenti del piano tenant = await self.db.get(Tenant, tenant_id) if not tenant: raise NotFoundError("tenant") user_count_result = await self.db.execute( select(func.count()).where(User.tenant_id == tenant_id, User.is_active == True) ) count = user_count_result.scalar_one() if count >= tenant.max_users: raise TenantLimitExceededError("utenti", tenant.max_users) # Verifica email univoca nel tenant existing = await self.db.execute( select(User).where( User.tenant_id == tenant_id, User.email == data.email.lower(), ) ) if existing.scalar_one_or_none(): raise ConflictError(f"Email '{data.email}' già registrata in questo tenant") # Un admin non può creare un super_admin if data.role == "super_admin" and not created_by.is_super_admin: raise ForbiddenError("Non puoi creare utenti super_admin") user = User( tenant_id=tenant_id, email=data.email.lower(), password_hash=hash_password(data.password), full_name=data.full_name, role=data.role, ) self.db.add(user) await self.db.flush() # ottieni l'ID return user async def get_user(self, user_id: uuid.UUID, tenant_id: uuid.UUID) -> User: result = await self.db.execute( select(User).where(User.id == user_id, User.tenant_id == tenant_id) ) user = result.scalar_one_or_none() if not user: raise NotFoundError("utente") return user async def list_users( self, tenant_id: uuid.UUID, page: int = 1, page_size: int = 25, ) -> tuple[list[User], int]: """Restituisce lista utenti paginata + totale.""" offset = (page - 1) * page_size total_result = await self.db.execute( select(func.count()).where(User.tenant_id == tenant_id) ) total = total_result.scalar_one() users_result = await self.db.execute( select(User) .where(User.tenant_id == tenant_id) .order_by(User.created_at.desc()) .offset(offset) .limit(page_size) ) users = list(users_result.scalars().all()) return users, total async def update_user( self, user_id: uuid.UUID, tenant_id: uuid.UUID, data: UserUpdateRequest, updated_by: User, ) -> User: user = await self.get_user(user_id, tenant_id) # Non si può modificare un super_admin if user.is_super_admin and not updated_by.is_super_admin: raise ForbiddenError("Non puoi modificare un super_admin") if data.full_name is not None: user.full_name = data.full_name if data.role is not None: user.role = data.role if data.is_active is not None: user.is_active = data.is_active return user async def reset_password( self, user_id: uuid.UUID, tenant_id: uuid.UUID, new_password: str, ) -> None: user = await self.get_user(user_id, tenant_id) user.password_hash = hash_password(new_password) async def delete_user( self, user_id: uuid.UUID, tenant_id: uuid.UUID, deleted_by: User, ) -> None: user = await self.get_user(user_id, tenant_id) if user.id == deleted_by.id: raise ForbiddenError("Non puoi eliminare il tuo stesso account") if user.is_super_admin: raise ForbiddenError("Non puoi eliminare un super_admin") # Soft delete (disabilita invece di eliminare) user.is_active = False