Files
boc/landvex-admin-backend/app/services/user_service.py
T
Bernt aee0f09db8 landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar
- Vision: Identify-modell, FAISS, OCR alla testade
- API: Alla 7 integrationstester passerade
- Upplösare: Entitetsupplösning verifierad
2026-07-05 06:41:32 +00:00

167 lines
6.4 KiB
Python

"""Business logic för användarhantering."""
from typing import Optional
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from fastapi import HTTPException, status
from app.models import User, UserRole, UserStatus, Tenant
from app.schemas import UserCreate, UserUpdate
from app.security import get_password_hash, verify_password, can_manage
class UserService:
def __init__(self, db: AsyncSession):
self.db = db
async def get_by_id(self, user_id: UUID) -> Optional[User]:
result = await self.db.execute(select(User).where(User.id == user_id))
return result.scalar_one_or_none()
async def get_by_email(self, email: str) -> Optional[User]:
result = await self.db.execute(select(User).where(User.email == email.lower().strip()))
return result.scalar_one_or_none()
async def list_users(
self,
tenant_id: Optional[UUID] = None,
role: Optional[UserRole] = None,
status: Optional[UserStatus] = None,
skip: int = 0,
limit: int = 100,
) -> tuple[list[User], int]:
query = select(User)
count_query = select(func.count()).select_from(User)
if tenant_id:
query = query.where(User.tenant_id == tenant_id)
count_query = count_query.where(User.tenant_id == tenant_id)
if role:
query = query.where(User.role == role)
count_query = count_query.where(User.role == role)
if status:
query = query.where(User.status == status)
count_query = count_query.where(User.status == status)
total_result = await self.db.execute(count_query)
total = total_result.scalar_one()
result = await self.db.execute(query.offset(skip).limit(limit).order_by(User.created_at.desc()))
return result.scalars().all(), total
async def create_user(self, data: UserCreate, created_by: Optional[User] = None) -> User:
existing = await self.get_by_email(data.email)
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User with this email already exists",
)
if data.tenant_id:
tenant = await self.db.execute(select(Tenant).where(Tenant.id == data.tenant_id))
if tenant.scalar_one_or_none() is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Tenant not found",
)
user = User(
email=data.email.lower().strip(),
first_name=data.first_name.strip(),
last_name=data.last_name.strip(),
role=data.role,
tenant_id=data.tenant_id,
status=UserStatus.ACTIVE if data.password else UserStatus.PENDING,
hashed_password=get_password_hash(data.password) if data.password else None,
)
self.db.add(user)
await self.db.commit()
await self.db.refresh(user)
return user
async def update_user(self, user_id: UUID, data: UserUpdate, actor: User) -> User:
user = await self.get_by_id(user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
if not actor.is_superuser and actor.id != user_id:
if not can_manage(actor.role, user.role):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Cannot modify user with equal or higher role",
)
if data.email and data.email.lower().strip() != user.email:
existing = await self.get_by_email(data.email)
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Email already in use",
)
user.email = data.email.lower().strip()
if data.first_name is not None:
user.first_name = data.first_name.strip()
if data.last_name is not None:
user.last_name = data.last_name.strip()
if data.role is not None:
if not actor.is_superuser and not can_manage(actor.role, data.role):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Cannot assign role equal or higher than your own",
)
user.role = data.role
if data.status is not None:
user.status = data.status
if data.tenant_id is not None:
user.tenant_id = data.tenant_id
await self.db.commit()
await self.db.refresh(user)
return user
async def delete_user(self, user_id: UUID, actor: User) -> None:
user = await self.get_by_id(user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
if not actor.is_superuser:
if not can_manage(actor.role, user.role):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Cannot delete user with equal or higher role",
)
if actor.id == user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Cannot delete yourself",
)
await self.db.delete(user)
await self.db.commit()
async def authenticate(self, email: str, password: str) -> Optional[User]:
user = await self.get_by_email(email)
if not user or not user.hashed_password:
return None
if not verify_password(password, user.hashed_password):
return None
if user.status != UserStatus.ACTIVE:
return None
return user
async def set_password(self, user: User, password: str) -> None:
user.hashed_password = get_password_hash(password)
user.status = UserStatus.ACTIVE
await self.db.commit()
async def change_password(self, user: User, current_password: str, new_password: str) -> None:
if not verify_password(current_password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Current password is incorrect",
)
user.hashed_password = get_password_hash(new_password)
await self.db.commit()