Init project structure

This commit is contained in:
k1nq
2025-11-22 13:56:45 +05:00
parent ad0025be28
commit 74330b292f
25 changed files with 1343 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
"""HTTP API routers and dependencies."""
+35
View File
@@ -0,0 +1,35 @@
"""Reusable FastAPI dependencies."""
from collections.abc import AsyncGenerator
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_session
from app.core.security import jwt_service, password_hasher
from app.repositories.user_repo import UserRepository
from app.services.auth_service import AuthService
from app.services.user_service import UserService
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
"""Provide a scoped database session."""
async for session in get_session():
yield session
def get_user_repository(session: AsyncSession = Depends(get_db_session)) -> UserRepository:
return UserRepository(session=session)
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:
return AuthService(
user_repository=repo,
password_hasher=password_hasher,
jwt_service=jwt_service,
)
+9
View File
@@ -0,0 +1,9 @@
"""Root API router that aggregates versioned routers."""
from fastapi import APIRouter
from app.api.v1 import auth, users
from app.core.config import settings
api_router = APIRouter()
api_router.include_router(users.router, prefix=settings.api_v1_prefix)
api_router.include_router(auth.router, prefix=settings.api_v1_prefix)
+1
View File
@@ -0,0 +1 @@
"""Version 1 API routers."""
+22
View File
@@ -0,0 +1,22 @@
"""Authentication API endpoints."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, status
from app.api.deps import get_auth_service
from app.models.token import LoginRequest, TokenResponse
from app.services.auth_service import AuthService, InvalidCredentialsError
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/token", response_model=TokenResponse)
async def login_for_access_token(
credentials: LoginRequest,
service: AuthService = Depends(get_auth_service),
) -> TokenResponse:
try:
user = await service.authenticate(credentials.email, credentials.password)
except InvalidCredentialsError as exc: # pragma: no cover - thin API
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc
return service.create_access_token(user)
+37
View File
@@ -0,0 +1,37 @@
"""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)