This commit is contained in:
e4www
2026-01-30 16:34:56 +03:00
commit 12f2ceaf9b
27 changed files with 1331 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
from fastapi import APIRouter
router = APIRouter(prefix="/health", tags=["health"])
@router.get("")
async def health():
return {"status": "ok"}

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from app.application.users.use_cases.create_user import (
CreateUserCommand,
CreateUserUseCase,
)
from app.application.users.use_cases.get_user import GetUserQuery, GetUserUseCase
from app.domain.users.exceptions import InvalidEmail, UserAlreadyExists, UserNotFound
from app.presentation.dependencies.users import get_create_user_uc, get_get_user_uc
from app.presentation.schemas.users import CreateUserRequest, UserResponse
router = APIRouter(prefix="/users", tags=["users"])
@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(
req: CreateUserRequest,
uc: CreateUserUseCase = Depends(get_create_user_uc),
) -> UserResponse:
try:
dto = await uc.execute(
CreateUserCommand(email=str(req.email), full_name=req.full_name)
)
return UserResponse(**dto.__dict__)
except InvalidEmail as e:
raise HTTPException(status_code=400, detail=str(e))
except UserAlreadyExists as e:
raise HTTPException(status_code=409, detail=str(e))
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: UUID,
uc: GetUserUseCase = Depends(get_get_user_uc),
) -> UserResponse:
try:
dto = await uc.execute(GetUserQuery(user_id=user_id))
return UserResponse(**dto.__dict__)
except UserNotFound as e:
raise HTTPException(status_code=404, detail=str(e))

View File

@@ -0,0 +1,30 @@
from __future__ import annotations
from fastapi import Depends
from app.application.users.use_cases.create_user import CreateUserUseCase
from app.application.users.use_cases.get_user import GetUserUseCase
from app.infrastructure.db.sqlalchemy.session import get_sessionmaker
from app.infrastructure.db.sqlalchemy.uow import SqlAlchemyUnitOfWork
from app.infrastructure.integrations.email.smtp_sender import SmtpWelcomeEmailSender
def get_uow() -> SqlAlchemyUnitOfWork:
return SqlAlchemyUnitOfWork(get_sessionmaker())
def get_email_sender() -> SmtpWelcomeEmailSender:
return SmtpWelcomeEmailSender()
def get_create_user_uc(
uow: SqlAlchemyUnitOfWork = Depends(get_uow),
email_sender: SmtpWelcomeEmailSender = Depends(get_email_sender),
) -> CreateUserUseCase:
return CreateUserUseCase(uow=uow, welcome_email_sender=email_sender)
def get_get_user_uc(
uow: SqlAlchemyUnitOfWork = Depends(get_uow),
) -> GetUserUseCase:
return GetUserUseCase(uow=uow)

28
app/presentation/main.py Normal file
View File

@@ -0,0 +1,28 @@
from __future__ import annotations
from fastapi import FastAPI
from app.infrastructure.db.sqlalchemy.models import Base
from app.infrastructure.db.sqlalchemy.session import get_engine
from app.presentation.api.v1.health import router as health_router
from app.presentation.api.v1.users import router as users_router
def create_app() -> FastAPI:
app = FastAPI(title="DDD Onion FastAPI Demo", version="1.0.0")
app.include_router(health_router)
app.include_router(users_router)
@app.on_event("startup")
async def on_startup() -> None:
# демо-миграция: создать таблицы при старте
# в проде делай Alembic, но файл models.py уже готов.
engine = get_engine()
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
return app
app = create_app()

View File

@@ -0,0 +1,19 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, EmailStr, Field
class CreateUserRequest(BaseModel):
email: EmailStr
full_name: str = Field(min_length=1, max_length=200)
class UserResponse(BaseModel):
id: UUID
email: EmailStr
full_name: str
is_active: bool
created_at: datetime