diff --git a/api/app_builder/dependencies.py b/api/app_builder/dependencies.py index 824ff83..dd1faf1 100644 --- a/api/app_builder/dependencies.py +++ b/api/app_builder/dependencies.py @@ -1,16 +1,15 @@ from fastapi import FastAPI -from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker +from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, + async_sessionmaker) from api.application.abstractions.uow import UnitOfWork from api.application.protocols.password_hasher import PasswordHasher -from api.application.usecase.user.create_user import CreateUser +from api.application.usecase.auth.create_user import CreateUser from api.domain.user.repository import UserRepository -from api.infrastructure.dependencies.adapters import ( - create_engine, - create_session_maker, - new_session, - new_unit_of_work, -) +from api.infrastructure.dependencies.adapters import (create_engine, + create_session_maker, + new_session, + new_unit_of_work) from api.infrastructure.dependencies.configs import app_settings from api.infrastructure.dependencies.protocols import get_password_hasher from api.infrastructure.dependencies.repositories import get_user_repository diff --git a/api/app_builder/main.py b/api/app_builder/main.py index 64bfacd..52eed1d 100644 --- a/api/app_builder/main.py +++ b/api/app_builder/main.py @@ -21,6 +21,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator: engine = app.dependency_overrides[AsyncEngine](app.dependency_overrides[Settings]()) async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) await conn.run_sync(Base.metadata.create_all) yield diff --git a/api/app_builder/routers.py b/api/app_builder/routers.py index 67e77d2..e326c71 100644 --- a/api/app_builder/routers.py +++ b/api/app_builder/routers.py @@ -1,8 +1,10 @@ from fastapi import FastAPI -from api.presentation.routers import healthcheck_router, user_router +from api.presentation.routers import (auth_router, healthcheck_router, + user_router) def init_routers(app: FastAPI) -> None: app.include_router(user_router) + app.include_router(auth_router) app.include_router(healthcheck_router) diff --git a/api/application/contracts/__init__.py b/api/application/contracts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/application/contracts/auth/__init__.py b/api/application/contracts/auth/__init__.py new file mode 100644 index 0000000..81cd9cd --- /dev/null +++ b/api/application/contracts/auth/__init__.py @@ -0,0 +1,3 @@ +from .auth_request import UserCreateRequest + +__all__ = ("UserCreateRequest",) diff --git a/api/application/contracts/auth/auth_request.py b/api/application/contracts/auth/auth_request.py new file mode 100644 index 0000000..aeae1b7 --- /dev/null +++ b/api/application/contracts/auth/auth_request.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class UserCreateRequest: + name: str + email: str + password: str diff --git a/api/application/contracts/user/__init__.py b/api/application/contracts/user/__init__.py index 870ee98..9b23d49 100644 --- a/api/application/contracts/user/__init__.py +++ b/api/application/contracts/user/__init__.py @@ -1,9 +1,8 @@ -from .user_request import GetUserByEmailRequest, UserCreateRequest +from .user_request import GetUserByEmailRequest from .user_response import UserDetaledResponse, UserResponse __all__ = ( "UserResponse", "UserDetaledResponse", - "UserCreateRequest", "GetUserByEmailRequest", ) diff --git a/api/application/contracts/user/user_request.py b/api/application/contracts/user/user_request.py index e7035dc..e9a9bae 100644 --- a/api/application/contracts/user/user_request.py +++ b/api/application/contracts/user/user_request.py @@ -1,13 +1,6 @@ from dataclasses import dataclass -@dataclass(frozen=True) -class UserCreateRequest: - name: str - email: str - password: str - - @dataclass(frozen=True) class GetUserByEmailRequest: email: str diff --git a/api/application/usecase/auth/__init__.py b/api/application/usecase/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/application/usecase/user/create_user.py b/api/application/usecase/auth/create_user.py similarity index 92% rename from api/application/usecase/user/create_user.py rename to api/application/usecase/auth/create_user.py index 00bf3dc..0f4f82e 100644 --- a/api/application/usecase/user/create_user.py +++ b/api/application/usecase/auth/create_user.py @@ -1,5 +1,5 @@ from api.application.abstractions import UnitOfWork -from api.application.contracts.user.user_request import UserCreateRequest +from api.application.contracts.auth.auth_request import UserCreateRequest from api.application.protocols.password_hasher import PasswordHasher from api.domain.user.model import User from api.domain.user.repository import UserRepository diff --git a/api/application/usecase/user/__init__.py b/api/application/usecase/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/infrastructure/dependencies/usecases.py b/api/infrastructure/dependencies/usecases.py index cf29589..7ccc671 100644 --- a/api/infrastructure/dependencies/usecases.py +++ b/api/infrastructure/dependencies/usecases.py @@ -4,7 +4,7 @@ from fastapi import Depends from api.application.abstractions.uow import UnitOfWork from api.application.protocols.password_hasher import PasswordHasher -from api.application.usecase.user.create_user import CreateUser +from api.application.usecase.auth.create_user import CreateUser from api.domain.user.repository import UserRepository from api.infrastructure.dependencies.stub import Stub @@ -14,4 +14,6 @@ def provide_create_user( uow: Annotated[UnitOfWork, Depends(Stub(UnitOfWork))], password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))], ) -> CreateUser: - return CreateUser(uow=uow, user_repository=user_repository, password_hasher=password_hasher) + return CreateUser( + uow=uow, user_repository=user_repository, password_hasher=password_hasher + ) diff --git a/api/infrastructure/persistence/models/user.py b/api/infrastructure/persistence/models/user.py index 7ef93b7..aad0522 100644 --- a/api/infrastructure/persistence/models/user.py +++ b/api/infrastructure/persistence/models/user.py @@ -7,7 +7,7 @@ from api.infrastructure.persistence.models.base import Base class UserModel(Base): - __tablename__ = "user" + __tablename__ = "users" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), diff --git a/api/infrastructure/persistence/repositories/user_repository.py b/api/infrastructure/persistence/repositories/user_repository.py index a81b6aa..30fe2f8 100644 --- a/api/infrastructure/persistence/repositories/user_repository.py +++ b/api/infrastructure/persistence/repositories/user_repository.py @@ -1,4 +1,4 @@ -from sqlalchemy import insert +from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from api.domain.user import User, UserRepository @@ -10,13 +10,26 @@ class SqlAlchemyUserRepository(UserRepository): self.session = session async def create_user(self, user: User) -> None: - stmt = insert(UserModel).values( - id=user.id.value, - name=user.name.value, - email=user.email.value, - hashed_password=user.hashed_password, + # stmt = insert(UserModel).values( + # id=user.id.value, + # name=user.name.value, + # email=user.email.value, + # hashed_password=user.hashed_password, + # ) + stmt = text( + """INSERT INTO users (id, name, email, hashed_password) + VALUES(:id, :name, :email, :hashed_password) + """ + ) + await self.session.execute( + stmt, + { + "id": str(user.id.value), + "name": user.name.value, + "email": user.email.value, + "hashed_password": user.hashed_password, + }, ) - await self.session.execute(stmt) async def get_user(self, filter: dict) -> User | None: pass diff --git a/api/presentation/routers/__init__.py b/api/presentation/routers/__init__.py index fff026a..3dc43b7 100644 --- a/api/presentation/routers/__init__.py +++ b/api/presentation/routers/__init__.py @@ -1,7 +1,9 @@ +from .auth import auth_router from .ping import healthcheck_router from .user import user_router __all__ = ( "healthcheck_router", + "auth_router", "user_router", ) diff --git a/api/presentation/routers/auth.py b/api/presentation/routers/auth.py new file mode 100644 index 0000000..3af3d31 --- /dev/null +++ b/api/presentation/routers/auth.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends + +from api.application.contracts.auth import UserCreateRequest +from api.application.usecase.auth.create_user import CreateUser +from api.infrastructure.dependencies.stub import Stub + +auth_router = APIRouter(prefix="/auth", tags=["Auth"]) + + +@auth_router.post("/register", status_code=201) +async def create_user( + request: UserCreateRequest, + usecase: Annotated[CreateUser, Depends(Stub(CreateUser))], +) -> None: + return await usecase.execute(request) diff --git a/api/presentation/routers/user.py b/api/presentation/routers/user.py index fbad2ff..bef5f84 100644 --- a/api/presentation/routers/user.py +++ b/api/presentation/routers/user.py @@ -2,8 +2,7 @@ from typing import Annotated from fastapi import APIRouter, Depends -from api.application.contracts.user import UserCreateRequest, UserResponse -from api.application.usecase.user.create_user import CreateUser +from api.application.contracts.user import UserResponse from api.infrastructure.dependencies.stub import Stub user_router = APIRouter(prefix="/users", tags=["Users"]) @@ -12,11 +11,3 @@ user_router = APIRouter(prefix="/users", tags=["Users"]) @user_router.get("/") async def get_all_users() -> list[UserResponse]: return [] - - -@user_router.post("/") -async def create_user( - request: UserCreateRequest, - usecase: Annotated[CreateUser, Depends(Stub(CreateUser))], -) -> None: - return await usecase.execute(request) diff --git a/config/api_config.yml b/config/api_config.yml index ed7f7bb..eb29195 100644 --- a/config/api_config.yml +++ b/config/api_config.yml @@ -1,5 +1,5 @@ db: - host: "db" + host: "localhost" port: 5432 database: "serviceman_db" user: "demo_user" diff --git a/poetry.lock b/poetry.lock index 161f707..7cdb516 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1132,6 +1132,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},