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:
+23
-23
@@ -4,7 +4,7 @@
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timezone
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
@@ -23,9 +23,9 @@ from sqlalchemy import (
|
||||
)
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
from app.core.timeutil import utcnow
|
||||
|
||||
|
||||
def _utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
# ─── Справочники: дополнения и фракции ───────────────────────────────────────
|
||||
@@ -38,7 +38,7 @@ class Expansion(SQLModel, table=True):
|
||||
name_ru: str = Field(sa_column=Column(String(64), nullable=False))
|
||||
is_base: bool = Field(sa_column=Column(Boolean, nullable=False, server_default="0"))
|
||||
sort_order: int = Field(default=0, nullable=False)
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
class Faction(SQLModel, table=True):
|
||||
@@ -56,7 +56,7 @@ class Faction(SQLModel, table=True):
|
||||
)
|
||||
)
|
||||
sort_order: int = Field(default=0, nullable=False)
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
# ─── Пользователи и идентичности ─────────────────────────────────────────────
|
||||
@@ -125,10 +125,10 @@ class User(SQLModel, table=True):
|
||||
# Выбранный титул (slug ачивки), отображаемый под ником. Задел: пока всегда NULL
|
||||
# (выдача ачивок игрокам — следующий этап).
|
||||
title_achievement_slug: str | None = Field(sa_column=Column(String(64), nullable=True))
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=_utcnow,
|
||||
sa_column_kwargs={"onupdate": _utcnow},
|
||||
default_factory=utcnow,
|
||||
sa_column_kwargs={"onupdate": utcnow},
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
@@ -150,7 +150,7 @@ class UserAchievement(SQLModel, table=True):
|
||||
)
|
||||
)
|
||||
achievement_slug: str = Field(sa_column=Column(String(64), nullable=False))
|
||||
earned_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
earned_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
class AuthIdentity(SQLModel, table=True):
|
||||
@@ -167,7 +167,7 @@ class AuthIdentity(SQLModel, table=True):
|
||||
)
|
||||
provider: str = Field(sa_column=Column(String(16), nullable=False))
|
||||
external_id: str = Field(sa_column=Column(String(64), nullable=False))
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
# ─── Группы и членство ───────────────────────────────────────────────────────
|
||||
@@ -182,10 +182,10 @@ class Group(SQLModel, table=True):
|
||||
Integer, ForeignKey("users.id", ondelete="RESTRICT"), nullable=False, index=True
|
||||
)
|
||||
)
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=_utcnow,
|
||||
sa_column_kwargs={"onupdate": _utcnow},
|
||||
default_factory=utcnow,
|
||||
sa_column_kwargs={"onupdate": utcnow},
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
@@ -209,7 +209,7 @@ class GroupMember(SQLModel, table=True):
|
||||
)
|
||||
)
|
||||
role: str = Field(default="member", sa_column=Column(String(16), nullable=False, server_default="member"))
|
||||
joined_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
joined_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
class GroupInvitation(SQLModel, table=True):
|
||||
@@ -237,7 +237,7 @@ class GroupInvitation(SQLModel, table=True):
|
||||
Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
)
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
class GroupExpansion(SQLModel, table=True):
|
||||
@@ -257,7 +257,7 @@ class GroupExpansion(SQLModel, table=True):
|
||||
Integer, ForeignKey("expansions.id", ondelete="RESTRICT"), nullable=False
|
||||
)
|
||||
)
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
# ─── Партии и участники ──────────────────────────────────────────────────────
|
||||
@@ -295,10 +295,10 @@ class Match(SQLModel, table=True):
|
||||
Integer, ForeignKey("users.id", ondelete="RESTRICT"), nullable=False, index=True
|
||||
)
|
||||
)
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=_utcnow,
|
||||
sa_column_kwargs={"onupdate": _utcnow},
|
||||
default_factory=utcnow,
|
||||
sa_column_kwargs={"onupdate": utcnow},
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
@@ -333,7 +333,7 @@ class MatchParticipant(SQLModel, table=True):
|
||||
eliminated: bool = Field(sa_column=Column(Boolean, nullable=False, server_default="0"))
|
||||
was_random: bool = Field(sa_column=Column(Boolean, nullable=False, server_default="0"))
|
||||
comment: str | None = Field(sa_column=Column(Text, nullable=True))
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
class MatchAttachment(SQLModel, table=True):
|
||||
@@ -359,7 +359,7 @@ class MatchAttachment(SQLModel, table=True):
|
||||
storage_path: str = Field(sa_column=Column(String(255), nullable=False))
|
||||
mime_type: str = Field(sa_column=Column(String(64), nullable=False))
|
||||
size_bytes: int = Field(sa_column=Column(Integer, nullable=False))
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
# ─── Уведомления ─────────────────────────────────────────────────────────────
|
||||
@@ -387,7 +387,7 @@ class Notification(SQLModel, table=True):
|
||||
body: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
link: str | None = Field(default=None, sa_column=Column(String(255), nullable=True))
|
||||
read_at: datetime | None = Field(default=None, sa_column=Column(DateTime, nullable=True))
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False, index=True)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False, index=True)
|
||||
|
||||
|
||||
# ─── Журнал аудита ───────────────────────────────────────────────────────────
|
||||
@@ -411,7 +411,7 @@ class AuditLog(SQLModel, table=True):
|
||||
payload: dict | None = Field(default=None, sa_column=Column(JSON, nullable=True))
|
||||
ip: str | None = Field(sa_column=Column(String(45), nullable=True))
|
||||
user_agent: str | None = Field(sa_column=Column(String(256), nullable=True))
|
||||
created_at: datetime = Field(default_factory=_utcnow, nullable=False)
|
||||
created_at: datetime = Field(default_factory=utcnow, nullable=False)
|
||||
|
||||
|
||||
# Заготовка под будущие вложения (НЕ в v1-миграции, добавится отдельно):
|
||||
|
||||
Reference in New Issue
Block a user