From 00addb971f93ae242422000d04694454b72d775c Mon Sep 17 00:00:00 2001 From: k1nq Date: Fri, 28 Nov 2025 14:28:26 +0500 Subject: [PATCH] feat: remove deprecated user API and related services; clean up imports and dependencies --- app/api/deps.py | 5 -- app/api/routes.py | 2 - app/api/v1/__init__.py | 2 - app/api/v1/users.py | 37 --------------- app/services/__init__.py | 3 +- app/services/user_service.py | 48 -------------------- tests/api/v1/test_users.py | 88 ------------------------------------ 7 files changed, 1 insertion(+), 184 deletions(-) delete mode 100644 app/api/v1/users.py delete mode 100644 app/services/user_service.py delete mode 100644 tests/api/v1/test_users.py diff --git a/app/api/deps.py b/app/api/deps.py index 921d605..b0f573a 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -27,7 +27,6 @@ from app.services.organization_service import ( OrganizationService, ) from app.services.task_service import TaskService -from app.services.user_service import UserService oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.api_v1_prefix}/auth/token") @@ -66,10 +65,6 @@ def get_deal_service(repo: DealRepository = Depends(get_deal_repository)) -> Dea return DealService(repository=repo) -def get_user_service(repo: UserRepository = Depends(get_user_repository)) -> UserService: - return UserService(user_repository=repo, password_hasher=password_hasher) - - def get_auth_service( repo: UserRepository = Depends(get_user_repository), ) -> AuthService: diff --git a/app/api/routes.py b/app/api/routes.py index 8c2b877..d10e8e5 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -9,13 +9,11 @@ from app.api.v1 import ( deals, organizations, tasks, - users, ) from app.core.config import settings api_router = APIRouter() api_router.include_router(auth.router, prefix=settings.api_v1_prefix) -api_router.include_router(users.router, prefix=settings.api_v1_prefix) api_router.include_router(organizations.router, prefix=settings.api_v1_prefix) api_router.include_router(contacts.router, prefix=settings.api_v1_prefix) api_router.include_router(deals.router, prefix=settings.api_v1_prefix) diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py index 66eb61c..5c02685 100644 --- a/app/api/v1/__init__.py +++ b/app/api/v1/__init__.py @@ -7,7 +7,6 @@ from . import ( deals, organizations, tasks, - users, ) __all__ = [ @@ -18,5 +17,4 @@ __all__ = [ "deals", "organizations", "tasks", - "users", ] diff --git a/app/api/v1/users.py b/app/api/v1/users.py deleted file mode 100644 index 5076fd4..0000000 --- a/app/api/v1/users.py +++ /dev/null @@ -1,37 +0,0 @@ -"""User API endpoints.""" -from __future__ import annotations - -from fastapi import APIRouter, Depends, HTTPException, status - -from app.api.deps import get_user_service -from app.models.user import UserCreate, UserRead -from app.services.user_service import UserAlreadyExistsError, UserNotFoundError, UserService - -router = APIRouter(prefix="/users", tags=["users"]) - - -@router.get("/", response_model=list[UserRead]) -async def list_users(service: UserService = Depends(get_user_service)) -> list[UserRead]: - users = await service.list_users() - return [UserRead.model_validate(user) for user in users] - - -@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED) -async def create_user( - user_in: UserCreate, - service: UserService = Depends(get_user_service), -) -> UserRead: - try: - user = await service.create_user(user_in) - except UserAlreadyExistsError as exc: # pragma: no cover - thin API layer - raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc - return UserRead.model_validate(user) - - -@router.get("/{user_id}", response_model=UserRead) -async def get_user(user_id: int, service: UserService = Depends(get_user_service)) -> UserRead: - try: - user = await service.get_user(user_id) - except UserNotFoundError as exc: # pragma: no cover - thin API layer - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc - return UserRead.model_validate(user) diff --git a/app/services/__init__.py b/app/services/__init__.py index 33049f3..b696124 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -22,5 +22,4 @@ from .task_service import ( # noqa: F401 TaskService, TaskServiceError, TaskUpdateData, -) -from .user_service import UserService # noqa: F401 \ No newline at end of file +) \ No newline at end of file diff --git a/app/services/user_service.py b/app/services/user_service.py deleted file mode 100644 index c46a943..0000000 --- a/app/services/user_service.py +++ /dev/null @@ -1,48 +0,0 @@ -"""User-related business logic.""" -from __future__ import annotations - -from collections.abc import Sequence - -from app.core.security import PasswordHasher -from app.models.user import User, UserCreate -from app.repositories.user_repo import UserRepository - - -class UserServiceError(Exception): - """Base class for user service errors.""" - - -class UserAlreadyExistsError(UserServiceError): - """Raised when attempting to create a user with duplicate email.""" - - -class UserNotFoundError(UserServiceError): - """Raised when user record cannot be located.""" - - -class UserService: - """Encapsulates user-related workflows.""" - - def __init__(self, user_repository: UserRepository, password_hasher: PasswordHasher) -> None: - self._repository = user_repository - self._password_hasher = password_hasher - - async def list_users(self) -> Sequence[User]: - return await self._repository.list() - - async def get_user(self, user_id: int) -> User: - user = await self._repository.get_by_id(user_id) - if user is None: - raise UserNotFoundError(f"User {user_id} not found") - return user - - async def create_user(self, data: UserCreate) -> User: - existing = await self._repository.get_by_email(data.email) - if existing is not None: - raise UserAlreadyExistsError(f"User {data.email} already exists") - - hashed_password = self._password_hasher.hash(data.password) - user = await self._repository.create(data=data, hashed_password=hashed_password) - await self._repository.session.commit() - await self._repository.session.refresh(user) - return user diff --git a/tests/api/v1/test_users.py b/tests/api/v1/test_users.py deleted file mode 100644 index 447f52d..0000000 --- a/tests/api/v1/test_users.py +++ /dev/null @@ -1,88 +0,0 @@ -"""API tests for user endpoints.""" -from __future__ import annotations - -import pytest -from httpx import AsyncClient -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker - -from app.core.security import password_hasher -from app.models.user import User - - -async def _seed_user( - session_factory: async_sessionmaker[AsyncSession], - *, - email: str, - name: str, -) -> int: - async with session_factory() as session: - user = User( - email=email, - hashed_password=password_hasher.hash("SeedPass123!"), - name=name, - is_active=True, - ) - session.add(user) - await session.commit() - return user.id - - -@pytest.mark.asyncio -async def test_create_user_endpoint_persists_user( - session_factory: async_sessionmaker[AsyncSession], - client: AsyncClient, -) -> None: - payload = { - "email": "api-user@example.com", - "name": "API User", - "password": "UserPass123!", - } - - response = await client.post("/api/v1/users/", json=payload) - - assert response.status_code == 201 - body = response.json() - assert body["email"] == payload["email"] - - async with session_factory() as session: - user = await session.get(User, body["id"]) - assert user is not None - assert user.email == payload["email"] - assert user.hashed_password != payload["password"] - - -@pytest.mark.asyncio -async def test_list_users_endpoint_returns_existing_users( - session_factory: async_sessionmaker[AsyncSession], - client: AsyncClient, -) -> None: - await _seed_user(session_factory, email="list-a@example.com", name="List A") - await _seed_user(session_factory, email="list-b@example.com", name="List B") - - response = await client.get("/api/v1/users/") - - assert response.status_code == 200 - data = response.json() - emails = {item["email"] for item in data} - assert {"list-a@example.com", "list-b@example.com"}.issubset(emails) - - -@pytest.mark.asyncio -async def test_get_user_endpoint_returns_single_user( - session_factory: async_sessionmaker[AsyncSession], - client: AsyncClient, -) -> None: - user_id = await _seed_user(session_factory, email="detail@example.com", name="Detail User") - - response = await client.get(f"/api/v1/users/{user_id}") - - assert response.status_code == 200 - payload = response.json() - assert payload["id"] == user_id - assert payload["email"] == "detail@example.com" - - -@pytest.mark.asyncio -async def test_get_user_endpoint_returns_404_for_missing_user(client: AsyncClient) -> None: - response = await client.get("/api/v1/users/999") - assert response.status_code == 404 \ No newline at end of file