finish user model definition
parent
bf7b53c8e8
commit
d7120bb0ba
|
|
@ -72,7 +72,9 @@ async def create_db_tables():
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
# 모델 import (테이블 메타데이터 등록용)
|
# 모델 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.lyric.models import Lyric # noqa: F401
|
||||||
from app.song.models import Song # noqa: F401
|
from app.song.models import Song # noqa: F401
|
||||||
from app.video.models import Video # noqa: F401
|
from app.video.models import Video # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,138 @@ Home 모듈 SQLAlchemy 모델 정의
|
||||||
이 모듈은 영상 제작 파이프라인의 핵심 데이터 모델을 정의합니다.
|
이 모듈은 영상 제작 파이프라인의 핵심 데이터 모델을 정의합니다.
|
||||||
- Project: 프로젝트(사용자 입력 이력) 관리
|
- Project: 프로젝트(사용자 입력 이력) 관리
|
||||||
- Image: 업로드된 이미지 URL 관리
|
- Image: 업로드된 이미지 URL 관리
|
||||||
|
- UserProject: User와 Project 간 M:N 관계 중계 테이블
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, List, Optional
|
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 sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from app.database.session import Base
|
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:
|
if TYPE_CHECKING:
|
||||||
from app.lyric.models import Lyric
|
from app.lyric.models import Lyric
|
||||||
from app.song.models import Song
|
from app.song.models import Song
|
||||||
|
from app.user.models import User
|
||||||
from app.video.models import Video
|
from app.video.models import Video
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,6 +158,8 @@ class Project(Base):
|
||||||
lyrics: 생성된 가사 목록
|
lyrics: 생성된 가사 목록
|
||||||
songs: 생성된 노래 목록
|
songs: 생성된 노래 목록
|
||||||
videos: 최종 영상 결과 목록
|
videos: 최종 영상 결과 목록
|
||||||
|
user_projects: User와의 M:N 관계 (중계 테이블 통한 연결)
|
||||||
|
users: 프로젝트에 참여한 사용자 목록 (Association Proxy)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "project"
|
__tablename__ = "project"
|
||||||
|
|
@ -123,6 +244,20 @@ class Project(Base):
|
||||||
lazy="selectin",
|
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 __repr__(self) -> str:
|
||||||
def truncate(value: str | None, max_len: int = 10) -> str:
|
def truncate(value: str | None, max_len: int = 10) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,19 @@
|
||||||
User 모듈 SQLAlchemy 모델 정의
|
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 datetime import date, datetime
|
||||||
from typing import Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, Boolean, DateTime, Index, String, func
|
from sqlalchemy import BigInteger, Boolean, Date, DateTime, Index, String, func
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from app.database.session import Base
|
from app.database.session import Base
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.home.models import UserProject
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
"""
|
"""
|
||||||
|
|
@ -32,25 +29,50 @@ class User(Base):
|
||||||
nickname: 카카오 닉네임 (선택)
|
nickname: 카카오 닉네임 (선택)
|
||||||
profile_image_url: 카카오 프로필 이미지 URL (선택)
|
profile_image_url: 카카오 프로필 이미지 URL (선택)
|
||||||
thumbnail_image_url: 카카오 썸네일 이미지 URL (선택)
|
thumbnail_image_url: 카카오 썸네일 이미지 URL (선택)
|
||||||
|
phone: 전화번호 (선택)
|
||||||
|
name: 실명 (선택)
|
||||||
|
birth_date: 생년월일 (선택)
|
||||||
|
gender: 성별 (선택)
|
||||||
is_active: 계정 활성화 상태 (기본 True)
|
is_active: 계정 활성화 상태 (기본 True)
|
||||||
is_admin: 관리자 여부 (기본 False)
|
is_admin: 관리자 여부 (기본 False)
|
||||||
|
role: 사용자 권한 (user, manager, admin 등)
|
||||||
|
is_deleted: 소프트 삭제 여부 (기본 False)
|
||||||
|
deleted_at: 삭제 일시
|
||||||
last_login_at: 마지막 로그인 일시
|
last_login_at: 마지막 로그인 일시
|
||||||
created_at: 계정 생성 일시
|
created_at: 계정 생성 일시
|
||||||
updated_at: 계정 정보 수정 일시
|
updated_at: 계정 정보 수정 일시
|
||||||
|
|
||||||
|
권한 체계:
|
||||||
|
- user: 일반 사용자 (기본값)
|
||||||
|
- manager: 매니저 (일부 관리 권한)
|
||||||
|
- admin: 관리자 (is_admin=True와 동일)
|
||||||
|
|
||||||
|
소프트 삭제:
|
||||||
|
- is_deleted=True로 설정 시 삭제된 것으로 처리
|
||||||
|
- 실제 데이터는 DB에 유지됨
|
||||||
|
|
||||||
|
Relationships:
|
||||||
|
user_projects: Project와의 M:N 관계 (중계 테이블 통한 연결)
|
||||||
|
projects: 사용자가 참여한 프로젝트 목록 (Association Proxy)
|
||||||
|
|
||||||
카카오 API 응답 필드 매핑:
|
카카오 API 응답 필드 매핑:
|
||||||
- kakao_id: id (카카오 회원번호)
|
- kakao_id: id (카카오 회원번호)
|
||||||
- email: kakao_account.email
|
- email: kakao_account.email
|
||||||
- nickname: kakao_account.profile.nickname 또는 properties.nickname
|
- nickname: kakao_account.profile.nickname 또는 properties.nickname
|
||||||
- profile_image_url: kakao_account.profile.profile_image_url
|
- profile_image_url: kakao_account.profile.profile_image_url
|
||||||
- thumbnail_image_url: kakao_account.profile.thumbnail_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"
|
__tablename__ = "user"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
Index("idx_user_kakao_id", "kakao_id", unique=True),
|
Index("idx_user_kakao_id", "kakao_id", unique=True),
|
||||||
Index("idx_user_email", "email"),
|
Index("idx_user_email", "email"),
|
||||||
|
Index("idx_user_phone", "phone"),
|
||||||
Index("idx_user_is_active", "is_active"),
|
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"),
|
Index("idx_user_created_at", "created_at"),
|
||||||
{
|
{
|
||||||
"mysql_engine": "InnoDB",
|
"mysql_engine": "InnoDB",
|
||||||
|
|
@ -107,6 +129,33 @@ class User(Base):
|
||||||
comment="카카오 썸네일 이미지 URL",
|
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="관리자 권한 여부",
|
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="계정 정보 수정 일시",
|
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:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<User("
|
f"<User("
|
||||||
f"id={self.id}, "
|
f"id={self.id}, "
|
||||||
f"kakao_id={self.kakao_id}, "
|
f"kakao_id={self.kakao_id}, "
|
||||||
f"nickname='{self.nickname}', "
|
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")>"
|
f")>"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -340,9 +340,6 @@ class LogSettings(BaseSettings):
|
||||||
prj_settings = ProjectSettings()
|
prj_settings = ProjectSettings()
|
||||||
apikey_settings = APIKeySettings()
|
apikey_settings = APIKeySettings()
|
||||||
db_settings = DatabaseSettings()
|
db_settings = DatabaseSettings()
|
||||||
security_settings = SecuritySettings()
|
|
||||||
kakao_settings = KakaoSettings()
|
|
||||||
notification_settings = NotificationSettings()
|
|
||||||
cors_settings = CORSSettings()
|
cors_settings = CORSSettings()
|
||||||
crawler_settings = CrawlerSettings()
|
crawler_settings = CrawlerSettings()
|
||||||
azure_blob_settings = AzureBlobSettings()
|
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.admin_manager import init_admin
|
||||||
from app.core.common import lifespan
|
from app.core.common import lifespan
|
||||||
from app.database.session import engine
|
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.home.api.routers.v1.home import router as home_router
|
||||||
from app.lyric.api.routers.v1.lyric import router as lyric_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
|
from app.song.api.routers.v1.song import router as song_router
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue