finish user model definition
parent
bf7b53c8e8
commit
d7120bb0ba
|
|
@ -72,7 +72,9 @@ async def create_db_tables():
|
|||
import asyncio
|
||||
|
||||
# 모델 import (테이블 메타데이터 등록용)
|
||||
from app.home.models import Image, Project # noqa: F401
|
||||
# 주의: User를 먼저 import해야 UserProject가 User를 참조할 수 있음
|
||||
from app.user.models import User # noqa: F401
|
||||
from app.home.models import Image, Project, UserProject # noqa: F401
|
||||
from app.lyric.models import Lyric # noqa: F401
|
||||
from app.song.models import Song # noqa: F401
|
||||
from app.video.models import Video # noqa: F401
|
||||
|
|
|
|||
|
|
@ -4,19 +4,138 @@ Home 모듈 SQLAlchemy 모델 정의
|
|||
이 모듈은 영상 제작 파이프라인의 핵심 데이터 모델을 정의합니다.
|
||||
- Project: 프로젝트(사용자 입력 이력) 관리
|
||||
- Image: 업로드된 이미지 URL 관리
|
||||
- UserProject: User와 Project 간 M:N 관계 중계 테이블
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from sqlalchemy import DateTime, Index, Integer, String, Text, func
|
||||
from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.database.session import Base
|
||||
|
||||
# =============================================================================
|
||||
# User-Project M:N 관계 중계 테이블
|
||||
# =============================================================================
|
||||
#
|
||||
# 설계 의도:
|
||||
# - User와 Project는 다대다(M:N) 관계입니다.
|
||||
# - 한 사용자는 여러 프로젝트에 참여할 수 있습니다.
|
||||
# - 한 프로젝트에는 여러 사용자가 참여할 수 있습니다.
|
||||
#
|
||||
# 중계 테이블 역할:
|
||||
# - UserProject 테이블이 두 테이블 간의 관계를 연결합니다.
|
||||
# - 각 레코드는 특정 사용자와 특정 프로젝트의 연결을 나타냅니다.
|
||||
# - 추가 속성(role, joined_at)으로 관계의 메타데이터를 저장합니다.
|
||||
#
|
||||
# 외래키 설정:
|
||||
# - user_id: User 테이블의 id를 참조 (ON DELETE CASCADE)
|
||||
# - project_id: Project 테이블의 id를 참조 (ON DELETE CASCADE)
|
||||
# - CASCADE 설정으로 부모 레코드 삭제 시 중계 레코드도 자동 삭제됩니다.
|
||||
#
|
||||
# 관계 방향:
|
||||
# - User.projects → UserProject → Project (사용자가 참여한 프로젝트 목록)
|
||||
# - Project.users → UserProject → User (프로젝트에 참여한 사용자 목록)
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class UserProject(Base):
|
||||
"""
|
||||
User-Project M:N 관계 중계 테이블
|
||||
|
||||
사용자와 프로젝트 간의 다대다 관계를 관리합니다.
|
||||
한 사용자는 여러 프로젝트에 참여할 수 있고,
|
||||
한 프로젝트에는 여러 사용자가 참여할 수 있습니다.
|
||||
|
||||
Attributes:
|
||||
id: 고유 식별자 (자동 증가)
|
||||
user_id: 사용자 외래키 (User.id 참조)
|
||||
project_id: 프로젝트 외래키 (Project.id 참조)
|
||||
role: 프로젝트 내 사용자 역할 (owner: 소유자, member: 멤버, viewer: 조회자)
|
||||
joined_at: 프로젝트 참여 일시
|
||||
|
||||
외래키 제약조건:
|
||||
- user_id → user.id (ON DELETE CASCADE)
|
||||
- project_id → project.id (ON DELETE CASCADE)
|
||||
|
||||
유니크 제약조건:
|
||||
- (user_id, project_id) 조합은 유일해야 함 (중복 참여 방지)
|
||||
"""
|
||||
|
||||
__tablename__ = "user_project"
|
||||
__table_args__ = (
|
||||
Index("idx_user_project_user_id", "user_id"),
|
||||
Index("idx_user_project_project_id", "project_id"),
|
||||
Index("idx_user_project_user_project", "user_id", "project_id", unique=True),
|
||||
{
|
||||
"mysql_engine": "InnoDB",
|
||||
"mysql_charset": "utf8mb4",
|
||||
"mysql_collate": "utf8mb4_unicode_ci",
|
||||
},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
autoincrement=True,
|
||||
comment="고유 식별자",
|
||||
)
|
||||
|
||||
# 외래키: User 테이블 참조
|
||||
# - BigInteger 사용 (User.id가 BigInteger이므로 타입 일치 필요)
|
||||
# - ondelete="CASCADE": User 삭제 시 연결된 UserProject 레코드도 삭제
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
BigInteger,
|
||||
ForeignKey("user.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="사용자 외래키 (User.id 참조)",
|
||||
)
|
||||
|
||||
# 외래키: Project 테이블 참조
|
||||
# - Integer 사용 (Project.id가 Integer이므로 타입 일치 필요)
|
||||
# - ondelete="CASCADE": Project 삭제 시 연결된 UserProject 레코드도 삭제
|
||||
project_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("project.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="프로젝트 외래키 (Project.id 참조)",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# Relationships (관계 설정)
|
||||
# ==========================================================================
|
||||
# back_populates: 양방향 관계 설정 (User.user_projects, Project.user_projects)
|
||||
# lazy="selectin": N+1 문제 방지를 위한 즉시 로딩
|
||||
# ==========================================================================
|
||||
user: Mapped["User"] = relationship(
|
||||
"User",
|
||||
back_populates="user_projects",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
project: Mapped["Project"] = relationship(
|
||||
"Project",
|
||||
back_populates="user_projects",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<UserProject("
|
||||
f"id={self.id}, "
|
||||
f"user_id={self.user_id}, "
|
||||
f"project_id={self.project_id}, "
|
||||
f"role='{self.role}'"
|
||||
f")>"
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.lyric.models import Lyric
|
||||
from app.song.models import Song
|
||||
from app.user.models import User
|
||||
from app.video.models import Video
|
||||
|
||||
|
||||
|
|
@ -39,6 +158,8 @@ class Project(Base):
|
|||
lyrics: 생성된 가사 목록
|
||||
songs: 생성된 노래 목록
|
||||
videos: 최종 영상 결과 목록
|
||||
user_projects: User와의 M:N 관계 (중계 테이블 통한 연결)
|
||||
users: 프로젝트에 참여한 사용자 목록 (Association Proxy)
|
||||
"""
|
||||
|
||||
__tablename__ = "project"
|
||||
|
|
@ -123,6 +244,20 @@ class Project(Base):
|
|||
lazy="selectin",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# User M:N 관계 (중계 테이블 UserProject 통한 연결)
|
||||
# ==========================================================================
|
||||
# back_populates: UserProject.project와 양방향 연결
|
||||
# cascade: Project 삭제 시 UserProject 레코드도 삭제 (User는 유지)
|
||||
# lazy="selectin": N+1 문제 방지
|
||||
# ==========================================================================
|
||||
user_projects: Mapped[List["UserProject"]] = relationship(
|
||||
"UserProject",
|
||||
back_populates="project",
|
||||
cascade="all, delete-orphan",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
def truncate(value: str | None, max_len: int = 10) -> str:
|
||||
if value is None:
|
||||
|
|
|
|||
|
|
@ -2,22 +2,19 @@
|
|||
User 모듈 SQLAlchemy 모델 정의
|
||||
|
||||
카카오 소셜 로그인 기반 사용자 관리 모델입니다.
|
||||
|
||||
주의: 이 모델은 현재 개발 중이므로 create_db_tables()에서 import하지 않습니다.
|
||||
테이블 생성이 필요할 때 app/database/session.py의 create_db_tables()에
|
||||
아래 import를 추가하세요:
|
||||
|
||||
from app.user.models import User # noqa: F401
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from datetime import date, datetime
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, DateTime, Index, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy import BigInteger, Boolean, Date, DateTime, Index, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.database.session import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.home.models import UserProject
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""
|
||||
|
|
@ -32,25 +29,50 @@ class User(Base):
|
|||
nickname: 카카오 닉네임 (선택)
|
||||
profile_image_url: 카카오 프로필 이미지 URL (선택)
|
||||
thumbnail_image_url: 카카오 썸네일 이미지 URL (선택)
|
||||
phone: 전화번호 (선택)
|
||||
name: 실명 (선택)
|
||||
birth_date: 생년월일 (선택)
|
||||
gender: 성별 (선택)
|
||||
is_active: 계정 활성화 상태 (기본 True)
|
||||
is_admin: 관리자 여부 (기본 False)
|
||||
role: 사용자 권한 (user, manager, admin 등)
|
||||
is_deleted: 소프트 삭제 여부 (기본 False)
|
||||
deleted_at: 삭제 일시
|
||||
last_login_at: 마지막 로그인 일시
|
||||
created_at: 계정 생성 일시
|
||||
updated_at: 계정 정보 수정 일시
|
||||
|
||||
권한 체계:
|
||||
- user: 일반 사용자 (기본값)
|
||||
- manager: 매니저 (일부 관리 권한)
|
||||
- admin: 관리자 (is_admin=True와 동일)
|
||||
|
||||
소프트 삭제:
|
||||
- is_deleted=True로 설정 시 삭제된 것으로 처리
|
||||
- 실제 데이터는 DB에 유지됨
|
||||
|
||||
Relationships:
|
||||
user_projects: Project와의 M:N 관계 (중계 테이블 통한 연결)
|
||||
projects: 사용자가 참여한 프로젝트 목록 (Association Proxy)
|
||||
|
||||
카카오 API 응답 필드 매핑:
|
||||
- kakao_id: id (카카오 회원번호)
|
||||
- email: kakao_account.email
|
||||
- nickname: kakao_account.profile.nickname 또는 properties.nickname
|
||||
- profile_image_url: kakao_account.profile.profile_image_url
|
||||
- thumbnail_image_url: kakao_account.profile.thumbnail_image_url
|
||||
- birth_date: kakao_account.birthday 또는 kakao_account.birthyear
|
||||
- gender: kakao_account.gender (male/female)
|
||||
"""
|
||||
|
||||
__tablename__ = "user"
|
||||
__table_args__ = (
|
||||
Index("idx_user_kakao_id", "kakao_id", unique=True),
|
||||
Index("idx_user_email", "email"),
|
||||
Index("idx_user_phone", "phone"),
|
||||
Index("idx_user_is_active", "is_active"),
|
||||
Index("idx_user_is_deleted", "is_deleted"),
|
||||
Index("idx_user_role", "role"),
|
||||
Index("idx_user_created_at", "created_at"),
|
||||
{
|
||||
"mysql_engine": "InnoDB",
|
||||
|
|
@ -107,6 +129,33 @@ class User(Base):
|
|||
comment="카카오 썸네일 이미지 URL",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# 추가 사용자 정보
|
||||
# ==========================================================================
|
||||
phone: Mapped[Optional[str]] = mapped_column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
comment="전화번호 (본인인증, 알림용)",
|
||||
)
|
||||
|
||||
name: Mapped[Optional[str]] = mapped_column(
|
||||
String(50),
|
||||
nullable=True,
|
||||
comment="실명 (법적 실명, 결제/계약 시 사용)",
|
||||
)
|
||||
|
||||
birth_date: Mapped[Optional[date]] = mapped_column(
|
||||
Date,
|
||||
nullable=True,
|
||||
comment="생년월일 (카카오 제공 또는 직접 입력)",
|
||||
)
|
||||
|
||||
gender: Mapped[Optional[str]] = mapped_column(
|
||||
String(10),
|
||||
nullable=True,
|
||||
comment="성별 (male: 남성, female: 여성)",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# 계정 상태 관리
|
||||
# ==========================================================================
|
||||
|
|
@ -124,6 +173,29 @@ class User(Base):
|
|||
comment="관리자 권한 여부",
|
||||
)
|
||||
|
||||
role: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="user",
|
||||
comment="사용자 권한 (user: 일반, manager: 매니저, admin: 관리자)",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# 소프트 삭제
|
||||
# ==========================================================================
|
||||
is_deleted: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="소프트 삭제 여부 (True: 삭제됨)",
|
||||
)
|
||||
|
||||
deleted_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
nullable=True,
|
||||
comment="삭제 일시",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# 시간 정보
|
||||
# ==========================================================================
|
||||
|
|
@ -148,12 +220,28 @@ class User(Base):
|
|||
comment="계정 정보 수정 일시",
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# Project M:N 관계 (중계 테이블 UserProject 통한 연결)
|
||||
# ==========================================================================
|
||||
# back_populates: UserProject.user와 양방향 연결
|
||||
# cascade: User 삭제 시 UserProject 레코드도 삭제 (Project는 유지)
|
||||
# lazy="selectin": N+1 문제 방지
|
||||
# ==========================================================================
|
||||
user_projects: Mapped[List["UserProject"]] = relationship(
|
||||
"UserProject",
|
||||
back_populates="user",
|
||||
cascade="all, delete-orphan",
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<User("
|
||||
f"id={self.id}, "
|
||||
f"kakao_id={self.kakao_id}, "
|
||||
f"nickname='{self.nickname}', "
|
||||
f"is_active={self.is_active}"
|
||||
f"role='{self.role}', "
|
||||
f"is_active={self.is_active}, "
|
||||
f"is_deleted={self.is_deleted}"
|
||||
f")>"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -340,9 +340,6 @@ class LogSettings(BaseSettings):
|
|||
prj_settings = ProjectSettings()
|
||||
apikey_settings = APIKeySettings()
|
||||
db_settings = DatabaseSettings()
|
||||
security_settings = SecuritySettings()
|
||||
kakao_settings = KakaoSettings()
|
||||
notification_settings = NotificationSettings()
|
||||
cors_settings = CORSSettings()
|
||||
crawler_settings = CrawlerSettings()
|
||||
azure_blob_settings = AzureBlobSettings()
|
||||
|
|
|
|||
4
main.py
4
main.py
|
|
@ -6,6 +6,10 @@ from scalar_fastapi import get_scalar_api_reference
|
|||
from app.admin_manager import init_admin
|
||||
from app.core.common import lifespan
|
||||
from app.database.session import engine
|
||||
|
||||
# 주의: User 모델을 먼저 import해야 UserProject가 User를 참조할 수 있음
|
||||
from app.user.models import User # noqa: F401
|
||||
|
||||
from app.home.api.routers.v1.home import router as home_router
|
||||
from app.lyric.api.routers.v1.lyric import router as lyric_router
|
||||
from app.song.api.routers.v1.song import router as song_router
|
||||
|
|
|
|||
Loading…
Reference in New Issue