Compare commits

..

No commits in common. "c0ee89f175b562c64481515b5da26c4972237974" and "c6e82f292d6f5570e971f73a220e891e9c968391" have entirely different histories.

10 changed files with 8 additions and 245 deletions

View File

@ -4,4 +4,3 @@ DB_PASSWORD=db_password
DB_HOST=localhost DB_HOST=localhost
DB_PORT=5432 DB_PORT=5432
DB_ECHO=True DB_ECHO=True
SECRET_KEY=1234567890abcdefghigklmnopqrstuvwxyz

View File

@ -1,7 +0,0 @@
"""
Проект: Lkeep
Автор: Иван Ашихмин
Год: 2025
Специально для проекта "Код на салфетке"
https://pressanybutton.ru/category/servis-na-fastapi/
"""

View File

@ -1,7 +0,0 @@
"""
Проект: Lkeep
Автор: Иван Ашихмин
Год: 2025
Специально для проекта "Код на салфетке"
https://pressanybutton.ru/category/servis-na-fastapi/
"""

View File

@ -1,28 +0,0 @@
from passlib.context import CryptContext
from lkeep.core.settings import settings
class AuthHandler:
"""
Обрабатывает аутентификационные запросы и обеспечивает безопасность пользовательских данных.
:ivar secret: Секретный ключ, используемый для дополнительной безопасности при генерации хешей.
:type secret: str
:ivar pwd_context: Контекст для использования bcrypt-алгоритма хеширования паролей.
:type pwd_context: CryptContext
"""
secret = settings.secret_key.get_secret_value()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
async def get_password_hash(self, password: str) -> str:
"""
Генерирует хэш-значение пароля для безопасного сохранения и сравнения.
:param password: Пароль пользователя, который нужно зашифровать.
:type password: str
:returns: Хешированный вариант пароля.
:rtype: str
"""
return self.pwd_context.hash(password)

View File

@ -1,56 +0,0 @@
"""
Проект: Lkeep
Автор: Иван Ашихмин
Год: 2025
Специально для проекта "Код на салфетке"
https://pressanybutton.ru/category/servis-na-fastapi/
"""
from fastapi import Depends, HTTPException
from sqlalchemy import insert
from sqlalchemy.exc import IntegrityError
from lkeep.apps.auth.schemas import CreateUser, UserReturnData
from lkeep.core.core_dependency.db_dependency import DBDependency
from lkeep.database.models import User
class UserManager:
"""
Класс для управления пользователями.
"""
def __init__(self, model: type[User] = User, db: DBDependency = Depends(DBDependency)) -> None:
"""
Инициализирует экземпляр класса.
:param model: Модель, используемая для работы с данными.
:type model: Type[User]
:param db: Зависимость от базы данных. По умолчанию используется Depends(DBDependency).
:type db: DBDependency
"""
self.db = db
self.model = model
async def create_user(self, user: CreateUser) -> UserReturnData:
"""
Создает нового пользователя в базе данных.
:param user: Объект с данными для создания пользователя.
:type user: CreateUser
:returns: Данные созданного пользователя.
:rtype: UserReturnData
:raises HTTPException: Если пользователь уже существует.
"""
async with self.db.db_session as session:
query = insert(self.model).values(**user.model_dump()).returning(self.model)
try:
result = await session.execute(query)
except IntegrityError:
raise HTTPException(status_code=400, detail="User already exists.")
await session.commit()
user_data = await result.scalar_one()
return UserReturnData(**user_data.__dict__)

View File

@ -1,79 +0,0 @@
"""
Проект: Lkeep
Автор: Иван Ашихмин
Год: 2025
Специально для проекта "Код на салфетке"
https://pressanybutton.ru/category/servis-na-fastapi/
"""
import datetime
import uuid
from pydantic import BaseModel, EmailStr
class GetUserByID(BaseModel):
"""
Класс для получения пользователя по его уникальному идентификатору (ID).
:ivar id: Уникальный идентификатор пользователя, может быть представлен как объект типа uuid.UUID или строкой.
:type id: uuid.UUID | str
"""
id: uuid.UUID | str
class GetUserByEmail(BaseModel):
"""
Класс для поиска пользователя по электронной почте.
:ivar email: Электронная почта пользователя.
:type email: EmailStr
"""
email: EmailStr
class RegisterUser(GetUserByEmail):
"""
Класс для регистрации пользователя, наследующий класс GetUserByEmail.
:ivar password: Пароль пользователя.
:type password: str
"""
password: str
class CreateUser(GetUserByEmail):
"""
Класс для создания пользователя.
:ivar hashed_password: Хэшированный пароль пользователя.
:type hashed_password: str
"""
hashed_password: str
class UserReturnData(GetUserByID, GetUserByEmail):
"""
Класс для представления данных пользователя, возвращаемых из API.
:ivar is_active: Статус активности пользователя.
:type is_active: bool
:ivar is_verified: Статус верификации пользователя.
:type is_verified: bool
:ivar is_superuser: Флаг, указывающий на наличие привилегий суперпользователя.
:type is_superuser: bool
:ivar created_at: Временная метка создания записи о пользователе.
:type created_at: datetime.datetime
:ivar updated_at: Временная метка последнего обновления записи о пользователе.
:type updated_at: datetime.datetime
"""
is_active: bool
is_verified: bool
is_superuser: bool
created_at: datetime.datetime
updated_at: datetime.datetime

View File

@ -1,18 +1,21 @@
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from lkeep.core.settings import settings
class DBDependency: class DBDependency:
""" """
Класс для управления зависимостями базы данных, используя SQLAlchemy. Класс для управления зависимостями базы данных, используя SQLAlchemy.
""" """
def __init__(self) -> None: def __init__(self, db_url: str, db_echo: bool) -> None:
""" """
Инициализирует экземпляр класса, отвечающего за взаимодействие с асинхронной базой данных. Инициализирует экземпляр класса, отвечающего за взаимодействие с асинхронной базой данных.
:param db_url: URL для подключения к базе данных.
:type db_url: str
:param db_echo: Флаг, определяющий вывод подробных логов при взаимодействии с базой данных.
:type db_echo: bool
""" """
self._engine = create_async_engine(url=settings.db_settings.db_url, echo=settings.db_settings.db_echo) self._engine = create_async_engine(url=db_url, echo=db_echo)
self._session_factory = async_sessionmaker(bind=self._engine, expire_on_commit=False, autocommit=False) self._session_factory = async_sessionmaker(bind=self._engine, expire_on_commit=False, autocommit=False)
@property @property

View File

@ -54,14 +54,9 @@ class Settings(BaseSettings):
:ivar db_settings: Экземпляр класса DBSettings, содержащий настройки базы данных. :ivar db_settings: Экземпляр класса DBSettings, содержащий настройки базы данных.
:type db_settings: DBSettings :type db_settings: DBSettings
:ivar secret_key: Секретный ключ для шифрования
:type secret_key: SecretStr
""" """
db_settings: DBSettings = DBSettings() db_settings: DBSettings = DBSettings()
secret_key: SecretStr
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
settings = Settings() settings = Settings()

56
poetry.lock generated
View File

@ -118,41 +118,6 @@ docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"]
gssauth = ["gssapi", "sspilib"] gssauth = ["gssapi", "sspilib"]
test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"] test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"]
[[package]]
name = "bcrypt"
version = "4.0.1"
description = "Modern password hashing for your software and your servers"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
{file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
{file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
{file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
]
[package.extras]
tests = ["pytest (>=3.2.1,!=3.3.0)"]
typecheck = ["mypy"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2024.12.14" version = "2024.12.14"
@ -709,24 +674,6 @@ files = [
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
] ]
[[package]]
name = "passlib"
version = "1.7.4"
description = "comprehensive password hashing framework supporting over 30 schemes"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
]
[package.extras]
argon2 = ["argon2-cffi (>=18.2.0)"]
bcrypt = ["bcrypt (>=3.1.0)"]
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
totp = ["cryptography"]
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.3.6" version = "4.3.6"
@ -777,7 +724,6 @@ files = [
[package.dependencies] [package.dependencies]
annotated-types = ">=0.6.0" annotated-types = ">=0.6.0"
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
pydantic-core = "2.27.2" pydantic-core = "2.27.2"
typing-extensions = ">=4.12.2" typing-extensions = ">=4.12.2"
@ -1480,4 +1426,4 @@ files = [
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.12" python-versions = ">=3.12"
content-hash = "3d4c2b89136e414e183f596de6faca17a5c9d78e0b2788ad4cb4fd598e486faa" content-hash = "b171a7f89cc3ee2207778b1e189fbb1821fccda26de00d0181156d0e183a5b75"

View File

@ -16,9 +16,6 @@ dependencies = [
"pydantic-settings (>=2.7.1,<3.0.0)", "pydantic-settings (>=2.7.1,<3.0.0)",
"alembic (>=1.14.0,<2.0.0)", "alembic (>=1.14.0,<2.0.0)",
"ruff (>=0.9.0,<0.10.0)", "ruff (>=0.9.0,<0.10.0)",
"pydantic[email] (>=2.10.5,<3.0.0)",
"passlib (>=1.7.4,<2.0.0)",
"bcrypt (==4.0.1)",
] ]
[build-system] [build-system]