Skip to content

Comments

Backend review#12

Open
IAmLamarr wants to merge 39 commits intomainfrom
frontend_prettier
Open

Backend review#12
IAmLamarr wants to merge 39 commits intomainfrom
frontend_prettier

Conversation

@IAmLamarr
Copy link
Collaborator

No description provided.

Copy link
Collaborator Author

@IAmLamarr IAmLamarr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Общие замечания

  • Нет линтеров / форматтеров => местами нарушается код-стайл
    Рекомендую поставить и сконфигурировать ruff
  • Нет README.md, хотелось бы видеть базовое описание проекта, как его запускать
  • Нет .env-example, непонятно какие переменные среды нужно заполнить чтобы стартовать проект, нужно ознакамливаться со всей кодовой базой перед запуском
  • Не зафиксирована версия python, на которой работает проект
    Можно зафиксировать в README.md или использовать poetry или uv для менеджмента зависимостей и версий python
  • Нет Dockerfile
  • "Слишком умные" роутеры, не реализовано разделение "сервис - репозиторий", из-за этого сильно дублируется код, связанный с получением тех или иных сущностей
  • Некорректная работа с роутерами
    Нужно выделять общие роуты через префиксы, это уменьшит количество ошибок при создании URL + даст возможность реализовывать свои dependencies в роутах, уменьшать дублирование логики
  • Магические числа в части работы с ролями
    Если хочется проверить наличие роли в рамках команды, нужно завязываться на name роли (а еще лучше сделать отдельный dependency для этой задачи)
  • Коды ответов HTTP проще указывать цифрой, а не импортировать из fastapi
    Коды везде одинаковые, не вижу смысла в импорте; Это уменьшит количество лишнего кода
  • Сложно читать из-за импортов
    Нужно стараться использовать конструкцию from ... import ...
    Пример:
import typing
import project_schemas
import user
import fastapi
import orm
import team
import project

...

@router.get("/api/team/{team_id}/projects", response_model=typing.List[project_schemas.ProjectResponse],
            status_code=fastapi.status.HTTP_200_OK)
def get_team_projects(team_id: int, current_user: user.User = fastapi.Depends(auth.get_current_user),
                      data_base: orm.Session = fastapi.Depends(db.get_db)):
    """Получить все проекты в команде team_id"""
    team_obj = data_base.query(team.Team).filter(team.Team.id == team_id).first()

    if not team_obj:
        raise fastapi.HTTPException(status_code=fastapi.status.HTTP_404_NOT_FOUND, detail="Команда не найдена")

    user_team = data_base.query(team.UserTeam).filter(team.UserTeam.user_id == current_user.id,
                                                      team.UserTeam.team_id == team_id).first()

    if not user_team:
        raise fastapi.HTTPException(status_code=fastapi.status.HTTP_403_FORBIDDEN,
                                    detail="У вас нет доступа к этой команде")

    projects = data_base.query(project.Project).filter(project.Project.team_id == team_id).all()

    return projects

Используем from import:

from typing import List
from project_schemas import ProjectResponse
from user import User
from fastapi import Depends, HTTPException
from orm import Session
from team import Team, UserTeam
from project import Project

...

@router.get("/api/team/{team_id}/projects", response_model=List[ProjectResponse],
            status_code=200)
def get_team_projects(team_id: int, current_user: User = Depends(auth.get_current_user),
                      data_base: Session = Depends(db.get_db)):
    """Получить все проекты в команде team_id"""
    team_obj = data_base.query(Team).filter(Team.id == team_id).first()

    if not team_obj:
        raise HTTPException(status_code=404, detail="Команда не найдена")

    user_team = data_base.query(UserTeam).filter(UserTeam.user_id == current_user.id,
                                                      UserTeam.team_id == team_id).first()

    if not user_team:
        raise HTTPException(status_code=403,
                                    detail="У вас нет доступа к этой команде")

    projects = data_base.query(Project).filter(Project.team_id == team_id).all()

    return projects

Второй вариант читается гораздо легче, т.к модули не упоминаются множество раз в коде

  • Подумать, точно ли нужны точечные сообщения для каждой ошибки или будет достаточно общих
    Можно ли отдавать в get_team_projects в случае 403 сообщение "Недостаточно прав" вместо "У вас нет доступа к этой команде"
    Если да, то можно вынести сделать реестр общих ошибок и выдавать их оттуда, вместо того, чтобы каждый раз писать логику для каждого роута

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Неполный gitignore, может не подойти для другой IDE
Игноры можно брать отсюда, в данном случае нужен игнор для "Python"

from app.core import db, security
from app.models import user

router = fastapi.APIRouter()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Указать тег auth?

Comment on lines +41 to +47
try:
data_base.commit()
data_base.refresh(u)
except exc.IntegrityError:
data_base.rollback()
raise fastapi.HTTPException(status_code=fastapi.status.HTTP_409_CONFLICT,
detail="Пользователь с таким email или именем уже существует")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не совсем корректно обрабатывать так проверку уникальности email и nickname
Если в будущем добавится новое поле с unique и нарушится его уникальность, вернется ошибка с тем что, что-то не то с email/nickname

Лучше запросить юзера из БД по такому email или такому nickname и если что-то нашлось - выдавать ошибку
Если не нашлось - добавлять нового юзера

if not stream_obj:
raise fastapi.HTTPException(status_code=fastapi.status.HTTP_404_NOT_FOUND, detail="Стрим не найден")

project_obj = data_base.query(project.Project).filter(project.Project.id == stream_obj.project_id).first()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет проверки project_obj по аналогии с stream_obj и user_team

if not stream_obj:
raise fastapi.HTTPException(status_code=fastapi.status.HTTP_404_NOT_FOUND, detail="Стрим не найден")

project_obj = data_base.query(project.Project).filter(project.Project.id == stream_obj.project_id).first()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет проверки project_obj

Comment on lines +2 to +3

Base = declarative_base() No newline at end of file
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно вынести id в базовую модель

Comment on lines +24 to +31
class UserTask(base.Base):
__tablename__ = "UserTask"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("Users.id"), nullable=False)
task_id = Column(Integer, ForeignKey("Tasks.id"), nullable=False)

user = relationship("User", backref="user_tasks")
task = relationship("Task", backref="user_tasks")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется, это должно быть в models/task.py

Comment on lines +6 to +8
class UserRole(str, Enum):
READER = "Reader"
EDITOR = "Editor"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Где-то выше уже есть такой enum

Comment on lines +15 to +19
@field_validator("email")
def validate_email(cls, value):
if not value:
raise fastapi.HTTPException(status_code=400, detail="Некорректный формат email")
return value
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А что не так со стандартной валидацией?

Comment on lines +7 to +14
@pytest.fixture(scope="session", autouse=True)
def load_env_and_models():
env_path = Path(__file__).parent.parent.parent / '.env'
load_dotenv(env_path)

import app.models.goal
import app.models.task
import app.models.meta No newline at end of file
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему происходит импорт внутри функции? Это плохая практика

В load_dotenv кажется можно не подавать такой путь, он по умолчанию будет искать .env в той директории, откуда запускается проект

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants