update redirect path format
parent
a6daff4e38
commit
cea23efac3
|
|
@ -152,7 +152,7 @@ async def download_and_save_song(
|
||||||
|
|
||||||
# 프론트엔드에서 접근 가능한 URL 생성
|
# 프론트엔드에서 접근 가능한 URL 생성
|
||||||
relative_path = f"/media/song/{today}/{unique_id}/{file_name}"
|
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}"
|
file_url = f"{base_url}{relative_path}"
|
||||||
logger.info(f"[download_and_save_song] URL generated - task_id: {task_id}, url: {file_url}")
|
logger.info(f"[download_and_save_song] URL generated - task_id: {task_id}, url: {file_url}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ User 모듈 SQLAlchemy 모델 정의
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from typing import TYPE_CHECKING, List, Optional
|
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 sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from app.database.session import Base
|
from app.database.session import Base
|
||||||
|
|
@ -246,6 +247,18 @@ class User(Base):
|
||||||
lazy="selectin",
|
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:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<User("
|
f"<User("
|
||||||
|
|
@ -373,3 +386,179 @@ class RefreshToken(Base):
|
||||||
f"expires_at={self.expires_at}"
|
f"expires_at={self.expires_at}"
|
||||||
f")>"
|
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"<SocialAccount("
|
||||||
|
f"id={self.id}, "
|
||||||
|
f"user_id={self.user_id}, "
|
||||||
|
f"platform='{self.platform}', "
|
||||||
|
f"platform_username='{self.platform_username}', "
|
||||||
|
f"is_active={self.is_active}"
|
||||||
|
f")>"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ from typing import Optional
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from config import prj_settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from app.user.exceptions import (
|
from app.user.exceptions import (
|
||||||
|
|
@ -111,7 +113,7 @@ class AuthService:
|
||||||
profile_image_url=user.profile_image_url,
|
profile_image_url=user.profile_image_url,
|
||||||
is_new_user=is_new_user,
|
is_new_user=is_new_user,
|
||||||
),
|
),
|
||||||
redirect_url="http://localhost:3000",
|
redirect_url=f"{prj_settings.PROJECT_DOMAIN}",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def refresh_tokens(
|
async def refresh_tokens(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue