159 lines
5.6 KiB
Python
159 lines
5.6 KiB
Python
from app.core.database import Base
|
||
from sqlalchemy import (
|
||
Boolean,
|
||
DateTime,
|
||
Enum,
|
||
ForeignKey,
|
||
Index,
|
||
Integer,
|
||
PrimaryKeyConstraint,
|
||
String,
|
||
func,
|
||
)
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
from starlette.authentication import BaseUser
|
||
|
||
|
||
class User(Base, BaseUser):
|
||
__tablename__ = "users"
|
||
|
||
id: Mapped[int] = mapped_column(
|
||
Integer, primary_key=True, nullable=False, autoincrement=True
|
||
)
|
||
username: Mapped[str] = mapped_column(
|
||
String(255), unique=True, nullable=False, index=True
|
||
)
|
||
email: Mapped[str] = mapped_column(
|
||
String(255), unique=True, nullable=False, index=True
|
||
)
|
||
hashed_password: Mapped[str] = mapped_column(String(60), nullable=False)
|
||
# age_level 컬럼을 Enum으로 정의
|
||
age_level_choices = ["10", "20", "30", "40", "50", "60", "70", "80"]
|
||
age_level: Mapped[str] = mapped_column(
|
||
Enum(*age_level_choices, name="age_level_enum"),
|
||
nullable=False,
|
||
default="10",
|
||
)
|
||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||
created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())
|
||
|
||
# One-to-many relationship with Post (DynamicMapped + lazy="dynamic")
|
||
posts_user: Mapped[list["Post"]] = relationship("Post", back_populates="user_posts")
|
||
|
||
# # Many-to-many relationship with Group
|
||
# user_groups: DynamicMapped["UserGroupAssociation"] = relationship(
|
||
# "UserGroupAssociation", back_populates="user", lazy="dynamic"
|
||
# )
|
||
# n:m 관계 (Group) – 최적의 lazy 옵션: selectin
|
||
group_user: Mapped[list["Group"]] = relationship(
|
||
"Group",
|
||
secondary="user_group_association",
|
||
back_populates="user_group",
|
||
lazy="selectin",
|
||
)
|
||
|
||
def __repr__(self) -> str:
|
||
return f"id={self.id}, username={self.username}"
|
||
|
||
@property
|
||
def is_authenticated(self) -> bool:
|
||
return self.is_active
|
||
|
||
@property
|
||
def display_name(self) -> str:
|
||
return self.username
|
||
|
||
@property
|
||
def identity(self) -> str:
|
||
return self.username
|
||
|
||
|
||
# 1:N Relationship - Posts
|
||
class Post(Base):
|
||
__tablename__ = "posts"
|
||
|
||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||
user_id: Mapped[int] = mapped_column(
|
||
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||
)
|
||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||
content: Mapped[str] = mapped_column(String(10000), nullable=False)
|
||
is_published: Mapped[bool] = mapped_column(Boolean, default=False)
|
||
created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())
|
||
updated_at: Mapped[DateTime] = mapped_column(
|
||
DateTime, server_default=func.now(), onupdate=func.now()
|
||
)
|
||
# tags: Mapped[dict] = mapped_column(MutableDict.as_mutable(JSONB), default=[]) // sqlite 지원 안함
|
||
view_count: Mapped[int] = mapped_column(Integer, default=0)
|
||
|
||
# Many-to-one relationship with User (using dynamic loading)
|
||
user_posts: Mapped["User"] = relationship("User", back_populates="posts_user")
|
||
|
||
def __repr__(self) -> str:
|
||
return f"Post(id={self.id}, user_id={self.user_id}, title={self.title})"
|
||
|
||
__table_args__ = (
|
||
Index("idx_posts_user_id", "user_id"),
|
||
Index("idx_posts_created_at", "created_at"),
|
||
Index(
|
||
"idx_posts_user_id_created_at", "user_id", "created_at"
|
||
), # Composite index
|
||
)
|
||
|
||
|
||
# N:M Relationship - Users and Groups
|
||
# Association table for many-to-many relationship
|
||
# N:M Association Table (중간 테이블)
|
||
class UserGroupAssociation(Base):
|
||
__tablename__ = "user_group_association"
|
||
|
||
user_id: Mapped[int] = mapped_column(
|
||
Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True
|
||
)
|
||
group_id: Mapped[int] = mapped_column(
|
||
Integer, ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True
|
||
)
|
||
|
||
# # 관계 정의
|
||
# user: Mapped["User"] = relationship("User", back_populates="user_groups")
|
||
# group: Mapped["Group"] = relationship("Group", back_populates="group_users")
|
||
# # 복합 기본 키 설정
|
||
|
||
# 기본 키 설정을 위한 __table_args__ 추가
|
||
__table_args__ = (PrimaryKeyConstraint("user_id", "group_id"),)
|
||
|
||
def __repr__(self) -> str:
|
||
return f"UserGroupAssociation(user_id={self.user_id}, group_id={self.group_id})"
|
||
|
||
|
||
# Group 테이블
|
||
class Group(Base):
|
||
__tablename__ = "groups"
|
||
|
||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||
name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
||
description: Mapped[str] = mapped_column(String(1000))
|
||
is_public: Mapped[bool] = mapped_column(Boolean, default=True)
|
||
created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())
|
||
updated_at: Mapped[DateTime] = mapped_column(
|
||
DateTime, server_default=func.now(), onupdate=func.now()
|
||
)
|
||
|
||
user_group: Mapped[list["User"]] = relationship(
|
||
"User",
|
||
secondary="user_group_association",
|
||
back_populates="group_user",
|
||
lazy="selectin",
|
||
)
|
||
|
||
# Group을 만든 사용자와 관계 (일반적인 1:N 관계)
|
||
def __repr__(self) -> str:
|
||
return f"Group(id={self.id}, name={self.name})"
|
||
|
||
__table_args__ = (
|
||
Index("idx_groups_name", "name"),
|
||
Index("idx_groups_is_public", "is_public"),
|
||
Index("idx_groups_created_at", "created_at"),
|
||
Index("idx_groups_composite", "is_public", "created_at"),
|
||
)
|