diff --git a/poetry.lock b/poetry.lock index b4139ef..a6201ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,6 +101,43 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + [[package]] name = "fastapi" version = "0.115.14" @@ -721,4 +758,4 @@ standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3) [metadata] lock-version = "2.1" python-versions = ">=3.12" -content-hash = "e9bc371ef3ba3a59fce2dbc0416fbb5be92bbccf98f36bd661ed55318975f3a1" +content-hash = "78991183ad2dfe443ab27c24820d79f492bb74ef261393364004ba457a42ba65" diff --git a/pyproject.toml b/pyproject.toml index 2aad5b3..aa16078 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "fastapi (>=0.115.14,<0.116.0)", "pyjwt (>=2.10.1,<3.0.0)", "passlib (>=1.7.4,<2.0.0)", + "email-validator (>=2.2.0,<3.0.0)", ] diff --git a/src/api/__init__.py b/src/api/__init__.py index 111ec7d..71ab889 100644 --- a/src/api/__init__.py +++ b/src/api/__init__.py @@ -1,8 +1,9 @@ from fastapi import APIRouter -from src.api.users import router as users_router -from src.api.tasks import router as tasks_router -router = APIRouter() +from src.api.v1 import router as v1_router +from src.core.settings import settings + +router = APIRouter(prefix=settings.api.prefix) + +router.include_router(router=v1_router) -router.include_router(router=users_router) -router.include_router(router=tasks_router) diff --git a/src/api/dependancies.py b/src/api/dependacies/db_dep.py similarity index 70% rename from src/api/dependancies.py rename to src/api/dependacies/db_dep.py index 495d080..b399166 100644 --- a/src/api/dependancies.py +++ b/src/api/dependacies/db_dep.py @@ -3,7 +3,7 @@ from typing import Annotated, AsyncGenerator from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession -from src.db.database import async_session_maker +from src.core.database import async_session_maker async def get_db() -> AsyncGenerator[AsyncSession, None]: @@ -11,6 +11,6 @@ async def get_db() -> AsyncGenerator[AsyncSession, None]: yield db -DBDep = Annotated[AsyncSession, Depends(get_db)] +sessionDep = Annotated[AsyncSession, Depends(get_db)] diff --git a/src/api/users.py b/src/api/users.py deleted file mode 100644 index 8d7a6eb..0000000 --- a/src/api/users.py +++ /dev/null @@ -1,3 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter(prefix="/users", tags=["Users"]) diff --git a/src/api/v1/__init__.py b/src/api/v1/__init__.py new file mode 100644 index 0000000..aad979a --- /dev/null +++ b/src/api/v1/__init__.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter +from src.api.v1.auth import router as auth_router +from src.api.v1.users import router as users_router +from src.api.v1.tasks import router as tasks_router +from src.core.settings import settings + +router = APIRouter(prefix=settings.api.v1.prefix) + +router.include_router(router=auth_router) +router.include_router(router=users_router) +router.include_router(router=tasks_router) diff --git a/src/api/v1/auth.py b/src/api/v1/auth.py new file mode 100644 index 0000000..e3cd8d1 --- /dev/null +++ b/src/api/v1/auth.py @@ -0,0 +1,13 @@ +from fastapi import APIRouter + +from src.api.dependacies.db_dep import sessionDep +from src.schemas.users import UserCreate +from src.core.settings import settings +from src.services.auth import AuthService + +router = APIRouter(prefix=settings.api.v1.auth, tags=['Auth']) + + +@router.post(path='/signup') +async def registration(session: sessionDep, credential: UserCreate): + await AuthService(session).registration(credential) diff --git a/src/api/tasks.py b/src/api/v1/tasks.py similarity index 100% rename from src/api/tasks.py rename to src/api/v1/tasks.py diff --git a/src/api/v1/users.py b/src/api/v1/users.py new file mode 100644 index 0000000..e354377 --- /dev/null +++ b/src/api/v1/users.py @@ -0,0 +1,5 @@ +from fastapi import APIRouter + +from src.core.settings import settings + +router = APIRouter(prefix=settings.api.v1.users, tags=["Users"]) diff --git a/src/api/auth.py b/src/core/__init__.py similarity index 100% rename from src/api/auth.py rename to src/core/__init__.py diff --git a/src/db/database.py b/src/core/database.py similarity index 92% rename from src/db/database.py rename to src/core/database.py index 001c371..12da284 100644 --- a/src/db/database.py +++ b/src/core/database.py @@ -4,7 +4,7 @@ from sqlalchemy import TIMESTAMP, func from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column -from src.settings import settings +from src.core.settings import settings engine = create_async_engine(settings.db.url, echo=True) diff --git a/src/settings.py b/src/core/settings.py similarity index 76% rename from src/settings.py rename to src/core/settings.py index a4c1397..affa4f7 100644 --- a/src/settings.py +++ b/src/core/settings.py @@ -7,6 +7,17 @@ BASE_DIR = Path(__file__).parent DB_PATH = BASE_DIR / "db/taskncoffee.db" +class ApiV1Prefix(BaseModel): + prefix: str = "/v1" + auth: str = "/auth" + users: str = "/users" + + +class ApiPrefix(BaseModel): + prefix: str = "/api" + v1: ApiV1Prefix = ApiV1Prefix() + + class DbSettings(BaseModel): url: str = f"sqlite+aiosqlite:///{DB_PATH}" @@ -22,7 +33,7 @@ class AccessToken(BaseSettings): class Settings(BaseSettings): - model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8') + api: ApiPrefix = ApiPrefix() db: DbSettings = DbSettings() access_token: AccessToken = AccessToken() diff --git a/src/main.py b/src/main.py index fa8d95d..7608f91 100644 --- a/src/main.py +++ b/src/main.py @@ -11,4 +11,4 @@ app = FastAPI() app.include_router(router=router) if __name__ == "__main__": - uvicorn.run("src.main:app", port=5000, log_level="info", reload=True) + uvicorn.run("src.main:app", port=8000, log_level="info", reload=True) diff --git a/src/models/tasks.py b/src/models/tasks.py index 6e9b4b9..fb685f2 100644 --- a/src/models/tasks.py +++ b/src/models/tasks.py @@ -4,7 +4,7 @@ from typing import Optional, TYPE_CHECKING from sqlalchemy import ForeignKey, Text, Date, Enum, String from sqlalchemy.orm import Mapped, mapped_column, relationship -from src.db.database import Base +from src.core.database import Base if TYPE_CHECKING: from src.models.users import UsersORM diff --git a/src/models/users.py b/src/models/users.py index d5d8668..cac4ab6 100644 --- a/src/models/users.py +++ b/src/models/users.py @@ -1,9 +1,9 @@ from typing import Optional, TYPE_CHECKING -from sqlalchemy import String, BigInteger, Integer, Boolean, VARCHAR +from sqlalchemy import String, BigInteger, Integer, Boolean from sqlalchemy.orm import Mapped, mapped_column, relationship -from src.db.database import Base +from src.core.database import Base if TYPE_CHECKING: from src.models.tasks import TasksORM diff --git a/src/repository/__init__.py b/src/repository/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/user_manager.py b/src/schemas/auth.py similarity index 100% rename from src/utils/user_manager.py rename to src/schemas/auth.py diff --git a/src/schemas/users.py b/src/schemas/users.py index e69de29..8af392d 100644 --- a/src/schemas/users.py +++ b/src/schemas/users.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel, EmailStr + + +class UserRead(BaseModel): + username: str + email: EmailStr | None + is_active: bool + is_superuser: bool + + +class UserCreate(BaseModel): + username: str + email: EmailStr | None = None + password: str + diff --git a/src/services/__init__.py b/src/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/services/auth.py b/src/services/auth.py new file mode 100644 index 0000000..a7b786b --- /dev/null +++ b/src/services/auth.py @@ -0,0 +1,11 @@ +from src.schemas.users import UserCreate +from src.services.base import BaseService + + +class AuthService(BaseService): + + + async def registration(self, data: UserCreate): + ... + + diff --git a/src/services/base.py b/src/services/base.py new file mode 100644 index 0000000..9451e22 --- /dev/null +++ b/src/services/base.py @@ -0,0 +1,9 @@ +from src.utils.db_manager import DBManager + + +class BaseService: + session: DBManager | None + + def __init__(self, session: DBManager): + self.session = session + diff --git a/src/utils/auth_manager.py b/src/utils/auth_manager.py new file mode 100644 index 0000000..30cfa3b --- /dev/null +++ b/src/utils/auth_manager.py @@ -0,0 +1,9 @@ +from passlib.context import CryptContext + + +class AuthManger: + pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + @classmethod + def verify_password(cls, plain_password, hashed_password): + return cls.pwd_context.verify(plain_password, hashed_password) \ No newline at end of file diff --git a/src/utils/db_manager.py b/src/utils/db_manager.py new file mode 100644 index 0000000..d4df98c --- /dev/null +++ b/src/utils/db_manager.py @@ -0,0 +1,12 @@ + + +class DBManager: + def __init__(self, session_factory): + self.session_factory = session_factory + + async def __aenter__(self): + self.session = self.session_factory() + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.session.rollback() + await self.session.close()