重构表结构:合并 profiles→users,展平 API 响应,新增计数器字段
- models: 删除 Profile 模型,字段合并到 User;User 新增云朵/公开/收藏计数器;Cloud 新增 favorite_count - schemas/serializers: 展平 UserOut/AuthOut/MeOut/AdminUserOut,移除嵌套 profile - deps: user.profile.role→user.role,移除 selectinload profile - auth/register: 注册时直接设置 user 字段,不再创建 Profile - clouds: 创建/删除云朵时同步 user.cloud_count 计数器,状态变化时同步 public_cloud_count - favorites: 点赞/取消时同步 cloud.favorite_count 计数器 - admin: 审批/隐藏/批量删时同步 public_cloud_count 计数器 - profiles/stats: 用计数器替代实时 COUNT 查询 - alembic: 新增 migration 合并 profiles 到 users,初始化 counters
This commit is contained in:
+11
-52
@@ -8,6 +8,7 @@ from sqlalchemy import (
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
Numeric,
|
||||
SmallInteger,
|
||||
Text,
|
||||
@@ -32,42 +33,28 @@ class TimestampMixin:
|
||||
|
||||
class User(Base, TimestampMixin):
|
||||
__tablename__ = "users"
|
||||
__table_args__ = (
|
||||
CheckConstraint("length(trim(username)) >= 2", name="ck_users_username_length"),
|
||||
CheckConstraint("role IN ('user', 'admin')", name="ck_users_role"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
|
||||
email: Mapped[str] = mapped_column(Text, unique=True, nullable=False)
|
||||
password_hash: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
email_verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
profile: Mapped["Profile"] = relationship(
|
||||
back_populates="user", cascade="all, delete-orphan", uselist=False, lazy="raise"
|
||||
)
|
||||
clouds: Mapped[list["Cloud"]] = relationship(back_populates="user", lazy="raise")
|
||||
|
||||
|
||||
class Profile(Base):
|
||||
__tablename__ = "profiles"
|
||||
__table_args__ = (
|
||||
CheckConstraint("length(trim(username)) >= 2", name="ck_profiles_username_length"),
|
||||
CheckConstraint("role IN ('user', 'admin')", name="ck_profiles_role"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
Uuid, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
username: Mapped[str] = mapped_column(Text, unique=True, nullable=False)
|
||||
avatar_path: Mapped[str | None] = mapped_column(Text)
|
||||
role: Mapped[str] = mapped_column(Text, nullable=False, default="user", server_default="user")
|
||||
is_disabled: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=False, server_default=text("false")
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), nullable=False
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
||||
)
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="profile", lazy="raise")
|
||||
cloud_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
public_cloud_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
collection_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
|
||||
clouds: Mapped[list["Cloud"]] = relationship(back_populates="user", lazy="raise")
|
||||
|
||||
|
||||
class RefreshSession(Base):
|
||||
@@ -163,28 +150,7 @@ class Cloud(Base, TimestampMixin):
|
||||
),
|
||||
Index("ix_clouds_user_id", "user_id"),
|
||||
Index("ix_clouds_cloud_type_id", "cloud_type_id"),
|
||||
Index("ix_clouds_user_captured", "user_id", text("captured_at DESC"), text("created_at DESC")),
|
||||
Index("ix_clouds_status_created", "status", text("created_at DESC")),
|
||||
Index(
|
||||
"ix_clouds_public_gallery",
|
||||
text("created_at DESC"),
|
||||
text("id DESC"),
|
||||
postgresql_where=text("status = 'approved' AND is_hidden = false"),
|
||||
),
|
||||
Index(
|
||||
"ix_clouds_public_map",
|
||||
"captured_at",
|
||||
postgresql_where=text(
|
||||
"status = 'approved' AND is_hidden = false "
|
||||
"AND latitude IS NOT NULL AND longitude IS NOT NULL"
|
||||
),
|
||||
),
|
||||
Index(
|
||||
"ix_clouds_public_type",
|
||||
"cloud_type_id",
|
||||
text("captured_at DESC"),
|
||||
postgresql_where=text("status = 'approved' AND is_hidden = false"),
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
|
||||
@@ -206,6 +172,7 @@ class Cloud(Base, TimestampMixin):
|
||||
is_hidden: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=False, server_default=text("false")
|
||||
)
|
||||
favorite_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
|
||||
user: Mapped[User] = relationship(back_populates="clouds", lazy="raise")
|
||||
cloud_type: Mapped[CloudType | None] = relationship(lazy="raise")
|
||||
@@ -239,19 +206,11 @@ class UserCollection(Base):
|
||||
|
||||
|
||||
class CloudFavorite(Base):
|
||||
"""用户对照片的点赞/收藏记录。"""
|
||||
|
||||
__tablename__ = "cloud_favorites"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "cloud_id", name="uq_cloud_favorites_user_cloud"),
|
||||
Index("ix_cloud_favorites_user_id", "user_id"),
|
||||
Index("ix_cloud_favorites_cloud_id", "cloud_id"),
|
||||
Index(
|
||||
"ix_cloud_favorites_user_created",
|
||||
"user_id",
|
||||
text("created_at DESC"),
|
||||
text("id DESC"),
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
|
||||
|
||||
Reference in New Issue
Block a user