update redirect path format

insta
Dohyun Lim 2026-01-21 16:35:48 +09:00
parent a6daff4e38
commit cea23efac3
23 changed files with 194 additions and 3 deletions

0
app/archive/__init__.py Normal file
View File

View File

View File

View File

View File

View File

View File

0
app/archive/models.py Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -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}")

View File

@ -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"<User("
@ -373,3 +386,179 @@ class RefreshToken(Base):
f"expires_at={self.expires_at}"
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")>"
)

View File

@ -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(