From cea23efac3efbbcd38113d4ce804692075b76254 Mon Sep 17 00:00:00 2001 From: Dohyun Lim Date: Wed, 21 Jan 2026 16:35:48 +0900 Subject: [PATCH] update redirect path format --- app/archive/__init__.py | 0 app/archive/api/__init__.py | 0 app/archive/api/archive_admin.py | 0 app/archive/api/routers/__init__.py | 0 app/archive/api/routers/v1/__init__.py | 0 app/archive/api/routers/v1/archive.py | 0 app/archive/dependency.py | 0 app/archive/models.py | 0 app/archive/schemas/__init__.py | 0 app/archive/schemas/archive_schema.py | 0 app/archive/services/__init__.py | 0 app/archive/services/archive.py | 0 app/archive/tests/__init__.py | 0 app/archive/tests/archive/__init__.py | 0 app/archive/tests/archive/conftest.py | 0 app/archive/tests/archive/test_db.py | 0 app/archive/tests/conftest.py | 0 app/archive/tests/test_db.py | 0 app/archive/worker/__init__.py | 0 app/archive/worker/archive_task.py | 0 app/song/worker/song_task.py | 2 +- app/user/models.py | 191 ++++++++++++++++++++++++- app/user/services/auth.py | 4 +- 23 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 app/archive/__init__.py create mode 100644 app/archive/api/__init__.py create mode 100644 app/archive/api/archive_admin.py create mode 100644 app/archive/api/routers/__init__.py create mode 100644 app/archive/api/routers/v1/__init__.py create mode 100644 app/archive/api/routers/v1/archive.py create mode 100644 app/archive/dependency.py create mode 100644 app/archive/models.py create mode 100644 app/archive/schemas/__init__.py create mode 100644 app/archive/schemas/archive_schema.py create mode 100644 app/archive/services/__init__.py create mode 100644 app/archive/services/archive.py create mode 100644 app/archive/tests/__init__.py create mode 100644 app/archive/tests/archive/__init__.py create mode 100644 app/archive/tests/archive/conftest.py create mode 100644 app/archive/tests/archive/test_db.py create mode 100644 app/archive/tests/conftest.py create mode 100644 app/archive/tests/test_db.py create mode 100644 app/archive/worker/__init__.py create mode 100644 app/archive/worker/archive_task.py diff --git a/app/archive/__init__.py b/app/archive/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/api/__init__.py b/app/archive/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/api/archive_admin.py b/app/archive/api/archive_admin.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/api/routers/__init__.py b/app/archive/api/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/api/routers/v1/__init__.py b/app/archive/api/routers/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/api/routers/v1/archive.py b/app/archive/api/routers/v1/archive.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/dependency.py b/app/archive/dependency.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/models.py b/app/archive/models.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/schemas/__init__.py b/app/archive/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/schemas/archive_schema.py b/app/archive/schemas/archive_schema.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/services/__init__.py b/app/archive/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/services/archive.py b/app/archive/services/archive.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/tests/__init__.py b/app/archive/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/tests/archive/__init__.py b/app/archive/tests/archive/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/tests/archive/conftest.py b/app/archive/tests/archive/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/tests/archive/test_db.py b/app/archive/tests/archive/test_db.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/tests/conftest.py b/app/archive/tests/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/tests/test_db.py b/app/archive/tests/test_db.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/worker/__init__.py b/app/archive/worker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/archive/worker/archive_task.py b/app/archive/worker/archive_task.py new file mode 100644 index 0000000..e69de29 diff --git a/app/song/worker/song_task.py b/app/song/worker/song_task.py index 0f77558..94bae88 100644 --- a/app/song/worker/song_task.py +++ b/app/song/worker/song_task.py @@ -152,7 +152,7 @@ async def download_and_save_song( # 프론트엔드에서 접근 가능한 URL 생성 relative_path = f"/media/song/{today}/{unique_id}/{file_name}" - base_url = f"http://{prj_settings.PROJECT_DOMAIN}" + base_url = f"{prj_settings.PROJECT_DOMAIN}" file_url = f"{base_url}{relative_path}" logger.info(f"[download_and_save_song] URL generated - task_id: {task_id}, url: {file_url}") diff --git a/app/user/models.py b/app/user/models.py index 88de7cd..72635fd 100644 --- a/app/user/models.py +++ b/app/user/models.py @@ -7,7 +7,8 @@ User 모듈 SQLAlchemy 모델 정의 from datetime import date, datetime from typing import TYPE_CHECKING, List, Optional -from sqlalchemy import BigInteger, Boolean, Date, DateTime, ForeignKey, Index, Integer, String, func +from sqlalchemy import BigInteger, Boolean, Date, DateTime, ForeignKey, Index, Integer, String, Text, func +from sqlalchemy.dialects.mysql import JSON from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database.session import Base @@ -246,6 +247,18 @@ class User(Base): lazy="selectin", ) + # ========================================================================== + # SocialAccount 1:N 관계 + # ========================================================================== + # 한 사용자는 여러 소셜 계정을 연동할 수 있음 (YouTube, Instagram, Facebook) + # ========================================================================== + social_accounts: Mapped[List["SocialAccount"]] = relationship( + "SocialAccount", + back_populates="user", + cascade="all, delete-orphan", + lazy="selectin", + ) + def __repr__(self) -> str: return ( f"" ) + + +class SocialAccount(Base): + """ + 소셜 계정 연동 테이블 + + 사용자가 연동한 외부 소셜 플랫폼 계정 정보를 저장합니다. + YouTube, Instagram, Facebook 계정 연동을 지원합니다. + + Attributes: + id: 고유 식별자 (자동 증가) + user_id: 사용자 외래키 (User.id 참조) + platform: 플랫폼 구분 (youtube, instagram, facebook) + access_token: OAuth 액세스 토큰 + refresh_token: OAuth 리프레시 토큰 (선택) + token_expires_at: 토큰 만료 일시 + scope: 허용된 권한 범위 + platform_user_id: 플랫폼 내 사용자 고유 ID + platform_username: 플랫폼 내 사용자명/핸들 + platform_data: 플랫폼별 추가 정보 (JSON) + is_active: 연동 활성화 상태 + connected_at: 연동 일시 + updated_at: 정보 수정 일시 + + 플랫폼별 platform_data 예시: + - YouTube: {"channel_id": "UC...", "channel_title": "채널명"} + - Instagram: {"business_account_id": "...", "facebook_page_id": "..."} + - Facebook: {"page_id": "...", "page_access_token": "..."} + + Relationships: + user: 연동된 사용자 (User 테이블 참조) + """ + + __tablename__ = "social_account" + __table_args__ = ( + Index("idx_social_account_user_id", "user_id"), + Index("idx_social_account_platform", "platform"), + Index("idx_social_account_is_active", "is_active"), + Index( + "uq_user_platform_account", + "user_id", + "platform", + "platform_user_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_id: Mapped[int] = mapped_column( + BigInteger, + ForeignKey("user.id", ondelete="CASCADE"), + nullable=False, + comment="사용자 외래키 (User.id 참조)", + ) + + # ========================================================================== + # 플랫폼 구분 + # ========================================================================== + platform: Mapped[str] = mapped_column( + String(20), + nullable=False, + comment="플랫폼 구분 (youtube, instagram, facebook)", + ) + + # ========================================================================== + # OAuth 토큰 정보 + # ========================================================================== + access_token: Mapped[str] = mapped_column( + Text, + nullable=False, + comment="OAuth 액세스 토큰", + ) + + refresh_token: Mapped[Optional[str]] = mapped_column( + Text, + nullable=True, + comment="OAuth 리프레시 토큰 (플랫폼에 따라 선택적)", + ) + + token_expires_at: Mapped[Optional[datetime]] = mapped_column( + DateTime, + nullable=True, + comment="토큰 만료 일시", + ) + + scope: Mapped[Optional[str]] = mapped_column( + Text, + nullable=True, + comment="허용된 권한 범위 (OAuth scope)", + ) + + # ========================================================================== + # 플랫폼 계정 식별 정보 + # ========================================================================== + platform_user_id: Mapped[str] = mapped_column( + String(100), + nullable=False, + comment="플랫폼 내 사용자 고유 ID", + ) + + platform_username: Mapped[Optional[str]] = mapped_column( + String(100), + nullable=True, + comment="플랫폼 내 사용자명/핸들 (@username)", + ) + + # ========================================================================== + # 플랫폼별 추가 정보 (JSON) + # ========================================================================== + platform_data: Mapped[Optional[dict]] = mapped_column( + JSON, + nullable=True, + comment="플랫폼별 추가 정보 (채널ID, 페이지ID 등)", + ) + + # ========================================================================== + # 연동 상태 + # ========================================================================== + is_active: Mapped[bool] = mapped_column( + Boolean, + nullable=False, + default=True, + comment="연동 활성화 상태 (비활성화 시 사용 중지)", + ) + + # ========================================================================== + # 시간 정보 + # ========================================================================== + connected_at: Mapped[datetime] = mapped_column( + DateTime, + nullable=False, + server_default=func.now(), + comment="연동 일시", + ) + + updated_at: Mapped[datetime] = mapped_column( + DateTime, + nullable=False, + server_default=func.now(), + onupdate=func.now(), + comment="정보 수정 일시", + ) + + # ========================================================================== + # User 관계 + # ========================================================================== + user: Mapped["User"] = relationship( + "User", + back_populates="social_accounts", + ) + + def __repr__(self) -> str: + return ( + f"" + ) diff --git a/app/user/services/auth.py b/app/user/services/auth.py index fbdc9a5..c0198ed 100644 --- a/app/user/services/auth.py +++ b/app/user/services/auth.py @@ -11,6 +11,8 @@ from typing import Optional from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession +from config import prj_settings + logger = logging.getLogger(__name__) from app.user.exceptions import ( @@ -111,7 +113,7 @@ class AuthService: profile_image_url=user.profile_image_url, is_new_user=is_new_user, ), - redirect_url="http://localhost:3000", + redirect_url=f"{prj_settings.PROJECT_DOMAIN}", ) async def refresh_tokens(