4a93bd1c44
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
from typing import Any, Dict, Optional
|
|
from uuid import uuid4
|
|
|
|
from fastapi import HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.security import get_password_hash
|
|
from app.models.user import User
|
|
from app.repositories.user_repository import UserRepository
|
|
|
|
|
|
class UserService:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.repository = UserRepository(db)
|
|
|
|
def list_users(self, current_user: Dict[str, Any], page: int = 1, page_size: int = 20) -> dict:
|
|
self._require_admin(current_user)
|
|
tenant_id = current_user["tenant_id"]
|
|
normalized_page = max(page, 1)
|
|
normalized_page_size = max(min(page_size, 100), 1)
|
|
users = self.repository.list_paginated(tenant_id, normalized_page, normalized_page_size)
|
|
return {
|
|
"items": [self._to_item(item) for item in users],
|
|
"page": normalized_page,
|
|
"page_size": normalized_page_size,
|
|
"total": self.repository.count(tenant_id),
|
|
}
|
|
|
|
def create_user(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
username: str,
|
|
display_name: str,
|
|
password: str,
|
|
role: str,
|
|
status_value: str,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
normalized_username = username.strip().lower()
|
|
if self.repository.get_by_username(normalized_username) is not None:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="username already exists")
|
|
user = User(
|
|
id=f"user-{uuid4().hex[:12]}",
|
|
tenant_id=current_user["tenant_id"],
|
|
username=normalized_username,
|
|
display_name=display_name.strip(),
|
|
password_hash=get_password_hash(password),
|
|
role=self._normalize_role(role),
|
|
status=self._normalize_status(status_value),
|
|
)
|
|
created = self.repository.create(user)
|
|
return self._to_item(created)
|
|
|
|
def update_user(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
user_id: str,
|
|
username: str,
|
|
display_name: str,
|
|
password: Optional[str],
|
|
role: str,
|
|
status_value: str,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
user = self._require_user(current_user, user_id)
|
|
normalized_username = username.strip().lower()
|
|
existing = self.repository.get_by_username(normalized_username)
|
|
if existing is not None and existing.id != user.id:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="username already exists")
|
|
user.username = normalized_username
|
|
user.display_name = display_name.strip()
|
|
user.role = self._normalize_role(role)
|
|
user.status = self._normalize_status(status_value)
|
|
if password and password.strip():
|
|
user.password_hash = get_password_hash(password.strip())
|
|
updated = self.repository.update(user)
|
|
return self._to_item(updated)
|
|
|
|
def _require_user(self, current_user: Dict[str, Any], user_id: str) -> User:
|
|
user = self.repository.get_by_id(user_id)
|
|
if user is None or user.tenant_id != current_user["tenant_id"]:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user not found")
|
|
return user
|
|
|
|
def _require_admin(self, current_user: Dict[str, Any]):
|
|
if current_user.get("role") != "admin":
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="forbidden")
|
|
|
|
def _normalize_role(self, role: str) -> str:
|
|
normalized_role = role.strip().lower()
|
|
if normalized_role not in {"admin", "member"}:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="invalid role")
|
|
return normalized_role
|
|
|
|
def _normalize_status(self, status_value: str) -> str:
|
|
normalized_status = status_value.strip().lower()
|
|
if normalized_status not in {"active", "disabled"}:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="invalid status")
|
|
return normalized_status
|
|
|
|
def _to_item(self, user: User) -> dict:
|
|
return {
|
|
"id": user.id,
|
|
"tenant_id": user.tenant_id,
|
|
"username": user.username,
|
|
"display_name": user.display_name,
|
|
"role": user.role,
|
|
"status": user.status,
|
|
"created_at": user.created_at.isoformat(),
|
|
}
|