Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions alembic/versions/f210b1391ab0_users_migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Users Migration

Revision ID: f210b1391ab0
Revises: ffb4e984a7e9
Create Date: 2026-02-25 12:37:10.316531

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'f210b1391ab0'
down_revision: Union[str, Sequence[str], None] = 'ffb4e984a7e9'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('telegram_id', sa.Integer(), nullable=False),
sa.Column('telegram_username', sa.String(length=256), nullable=True),
sa.Column('create_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('telegram_id')
)
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions app/core/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Импорты класса Base и всех моделей Alembic."""

from app.core.database import Base # noqa
from app.models.users import User # noqa
from app.models.words import Word # noqa
75 changes: 75 additions & 0 deletions app/crud/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import logging

from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.users import User
from app.schemas.users import UserCreateSchema

logger = logging.getLogger(__name__)


class CRUDUser:

async def create_user(self, user: UserCreateSchema, session: AsyncSession):
try:
new_user = User(**user.model_dump())
session.add(new_user)
await session.flush()
await session.commit()
await session.refresh(new_user)
logger.info(
f"Добавлен пользователь '{new_user.telegram_username}' id: {new_user.telegram_id} "
)
return new_user
except SQLAlchemyError as e:
logger.error(f"Ошибка при добавлении пользователя: {e}")
raise

async def get_all_users(self, session: AsyncSession):
try:
query = select(User)
result = await session.execute(query)
users = result.scalars().all()
logger.info("Получен список всех пользователей")
return users
except SQLAlchemyError as e:
logger.error(
f"Ошибка при получении списка всех пользователей: {e}"
)
raise

async def get_user(self, telegram_id: int, session: AsyncSession):
try:
query = select(User).where(User.telegram_id == telegram_id)
result = await session.execute(query)
user = result.scalars().first()
if user:
logger.info(
f"Получен пользователь: '{user.telegram_username}' id={user.telegram_id}"
)
return user
else:
logger.warning(
f"Пользователь id={telegram_id} не найден в базе"
)
raise
except SQLAlchemyError as e:
logger.error(
f"Ошибка при получении пользователя id={telegram_id}: {e}"
)
raise

async def delete_user(self, user: User, session: AsyncSession):
try:
await session.delete(user)
await session.commit()
logger.info(
f"Пользователь: '{user.telegram_username}' id={user.telegram_id} удалён"
)
except SQLAlchemyError as e:
logger.error(
f"Ошибка при удалении пользователя '{user.telegram_username}' id={user.telegram_id}: {e}"
)
raise
4 changes: 3 additions & 1 deletion app/crud/words.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ async def get_word(self, word: str, session: AsyncSession):
result = await session.execute(query)
find_word = result.scalars().first()
if find_word:
logger.info(f"Получено слово: '{find_word}'")
logger.info(
f"Получено слово: '{find_word.english} - {find_word.russian}'"
)
else:
logger.warning(f"Слово '{word}' не найдено в базе")
return find_word
Expand Down
2 changes: 2 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from app.core.config import settings
from app.core.logging import setup_logging
from app.routers.questions import question_router
from app.routers.users import user_router
from app.routers.words import word_router

setup_logging()
Expand All @@ -28,3 +29,4 @@ async def lifespan(app: FastAPI):

app.include_router(word_router)
app.include_router(question_router)
app.include_router(user_router)
19 changes: 19 additions & 0 deletions app/models/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from datetime import datetime

from sqlalchemy import DateTime, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column

from app.core.database import Base


class User(Base):
__tablename__ = "users"

id: Mapped[int] = mapped_column(Integer, primary_key=True)
telegram_id: Mapped[int] = mapped_column(
Integer, nullable=False, unique=True
)
telegram_username: Mapped[str] = mapped_column(String(256), nullable=True)
create_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
6 changes: 3 additions & 3 deletions app/routers/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from app.core.database import get_session
from app.crud.words import CRUDWord
from app.schemas.words import QuestionResponseSchema
from app.schemas.questions import QuestionReadSchema

question_router = APIRouter(
prefix="/questions",
Expand All @@ -18,11 +18,11 @@


@question_router.get(
"", response_model=QuestionResponseSchema, summary="Получение вопроса"
"", response_model=QuestionReadSchema, summary="Получение вопроса"
)
async def get_question(
session: AsyncSession = Depends(get_session),
) -> QuestionResponseSchema:
) -> QuestionReadSchema:
words = await word_crud.get_all_words(session)
if len(words) < 4:
return {"error": "В базее должно быть минимум 4 слова"}
Expand Down
88 changes: 88 additions & 0 deletions app/routers/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_session
from app.crud.users import CRUDUser
from app.schemas.users import UserCreateSchema, UserReadSchema

user_router = APIRouter(
prefix="/users",
tags=[
"Пользователи",
],
)

user_crud = CRUDUser()


@user_router.post(
"", response_model=UserReadSchema, summary="Добавление пользователя"
)
async def add_user(
user: UserCreateSchema, session: AsyncSession = Depends(get_session)
) -> UserReadSchema:
try:
add_user = await user_crud.create_user(user, session)
return add_user
except Exception as e:
raise e


@user_router.delete("/{telegram_id}", summary="Удаление Пользователя")
async def delete_user(
telegram_id: int, session: AsyncSession = Depends(get_session)
) -> dict:
user = await user_crud.get_user(telegram_id, session)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Пользователь не найден",
)
try:
await user_crud.delete_user(user, session)
return {"detail": f"Пользователь id={telegram_id} удален"}
except Exception:
raise HTTPException(
status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Ошибка при удалении пользователя",
)


@user_router.get(
"/all",
response_model=list[UserReadSchema],
summary="Получение всех пользователей",
)
async def get_all_users(
session: AsyncSession = Depends(get_session),
) -> list[UserReadSchema]:
try:
users = await user_crud.get_all_users(session)
if not users:
raise HTTPException(
status_code=status.HTT_404_NOT_FOUND,
detail="Список пользоввателей отсутсттвует",
)
return users
except Exception as e:
raise e


@user_router.get(
"/{telegram_id}",
response_model=UserReadSchema,
summary="Получение пользователя по telegram_id",
)
async def get_user(
telegram_id: int, session: AsyncSession = Depends(get_session)
) -> UserReadSchema:
try:
user = await user_crud.get_user(telegram_id, session)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Пользователь id={telegram_id} не найден",
)
return user
except Exception as e:
raise e
8 changes: 8 additions & 0 deletions app/schemas/questions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel


class QuestionReadSchema(BaseModel):
word: str
options: list[str]
correct: str
direction: str
20 changes: 20 additions & 0 deletions app/schemas/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from datetime import datetime

from pydantic import BaseModel


class UserBaseSchema(BaseModel):
telegram_id: int
telegram_username: str | None = None


class UserCreateSchema(UserBaseSchema):
pass


class UserReadSchema(UserBaseSchema):
id: int
create_at: datetime

class Config:
orm_mode = True