Simplify: единый источник времени, версии партии и запросов
Проход /simplify по backend/app: - Три источника «текущего времени» (_utcnow в models.py и match_service.py при живом timeutil.utcnow) сведены к одному — это прямо инвариант из CLAUDE.md. - Бамп версии партии из двух независимых мест собран в match_service.touch: следующая точка мутации, не трогающая строку matches, теперь имеет очевидный способ сделать правильно. - membership_service переиспользует group_service.get_membership вместо трёх копий одного запроса; защита последнего владельца — один хелпер на удаление и смену роли вместо двух похожих блоков. - Убраны N+1: участники страницы партий и ники пригласивших берутся одним запросом вместо запроса на строку (20 партий = 20 лишних запросов с двумя join каждый). - Счётчики партий считает СУБД (COUNT/MAX) вместо выгрузки всех строк ради len() и max() в Python. #8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
This commit is contained in:
@@ -11,7 +11,6 @@ from sqlmodel import Session, select
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.errors import ConflictError, NotFoundError
|
||||
from app.core.timeutil import utcnow
|
||||
from app.models import Match, MatchAttachment, User
|
||||
|
||||
MAX_ATTACHMENTS = 10
|
||||
@@ -40,17 +39,11 @@ def file_path(att: MatchAttachment) -> Path:
|
||||
return Path(settings.upload_dir) / att.storage_path
|
||||
|
||||
|
||||
def _touch(session: Session, match: Match) -> None:
|
||||
"""Двинуть версию партии: набор вложений виден в MatchRead, а сама строка matches
|
||||
при работе с ними не меняется — без этого оптимистичная блокировка проспала бы
|
||||
конкурентную правку."""
|
||||
match.updated_at = utcnow()
|
||||
session.add(match)
|
||||
|
||||
|
||||
def add_photo(
|
||||
session: Session, match: Match, user: User, content: bytes, ext: str, mime: str
|
||||
) -> MatchAttachment:
|
||||
from app.services import match_service # избегаем цикла импорта
|
||||
|
||||
if count(session, match.id) >= MAX_ATTACHMENTS: # type: ignore[arg-type]
|
||||
raise ConflictError(f"Можно прикрепить не более {MAX_ATTACHMENTS} файлов.")
|
||||
att = MatchAttachment(
|
||||
@@ -69,7 +62,7 @@ def add_photo(
|
||||
abs_path.write_bytes(content)
|
||||
att.storage_path = rel
|
||||
session.add(att)
|
||||
_touch(session, match)
|
||||
match_service.touch(session, match)
|
||||
session.commit()
|
||||
session.refresh(att)
|
||||
return att
|
||||
@@ -83,6 +76,8 @@ def get_for_match(session: Session, match_id: int, att_id: int) -> MatchAttachme
|
||||
|
||||
|
||||
def delete(session: Session, match: Match, att_id: int) -> None:
|
||||
from app.services import match_service # избегаем цикла импорта
|
||||
|
||||
att = get_for_match(session, match.id, att_id) # type: ignore[arg-type]
|
||||
path = file_path(att)
|
||||
if path.exists():
|
||||
@@ -91,7 +86,7 @@ def delete(session: Session, match: Match, att_id: int) -> None:
|
||||
except OSError:
|
||||
pass
|
||||
session.delete(att)
|
||||
_touch(session, match)
|
||||
match_service.touch(session, match)
|
||||
session.commit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user