fix: 배포환경에 맞게 공유URL 적용되도록 수정
This commit is contained in:
parent
51ef659f16
commit
c7149deb1f
7
.gitignore
vendored
7
.gitignore
vendored
@ -32,8 +32,11 @@ media/
|
||||
|
||||
|
||||
*.ipynb_checkpoint*
|
||||
# Static files
|
||||
static/
|
||||
# Static files (공유 기본 이미지는 예외로 추적)
|
||||
static/*
|
||||
!static/images/
|
||||
static/images/*
|
||||
!static/images/ado2_image.png
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
@ -69,8 +69,8 @@ PROJECT_DOMAIN=localhost:8000 # 프로젝트 도메인 (호스트:포
|
||||
PROJECT_VERSION=0.1.0 # 프로젝트 버전
|
||||
DESCRIPTION=FastAPI 기반 CastAD 프로젝트 # 프로젝트 설명
|
||||
ADMIN_BASE_URL=/admin # 관리자 페이지 기본 URL
|
||||
SHARE_FRONTEND_URL=https://ado2.o2osolution.ai # 공유 링크가 이동할 프론트엔드 공개 URL
|
||||
SHARE_DEFAULT_IMAGE_URL=https://ado2.o2osolution.ai/assets/images/hero-background.png # 포스터가 없을 때의 절대 이미지 URL
|
||||
SHARE_FRONTEND_URL=https://ado2.o2osolution.ai # 공유 페이지 → 영상 상세 이동 프론트 URL (로컬: http://localhost:3000, 테스트: https://dev.castad.net)
|
||||
SHARE_DEFAULT_IMAGE_URL= # 포스터 없을 때 OG 이미지 (비우면 API /static/images/ado2_image.png)
|
||||
DEBUG=True # 디버그 모드 (True: 개발, False: 운영)
|
||||
|
||||
# ================================
|
||||
|
||||
@ -17,8 +17,8 @@ import json
|
||||
from collections import defaultdict
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@ -58,11 +58,11 @@ from app.video.schemas.video_schema import (
|
||||
VideoRenderData,
|
||||
VideoThumbnailItem,
|
||||
)
|
||||
from app.video.services.share_page import build_video_share_html, get_video_share_data
|
||||
from app.video.services.share_page import build_video_share_html, get_video_share_data
|
||||
from app.video.worker.video_task import download_and_upload_video_to_blob
|
||||
|
||||
|
||||
from config import creatomate_settings, prj_settings
|
||||
from config import creatomate_settings, prj_settings
|
||||
|
||||
logger = get_logger("video")
|
||||
|
||||
@ -1093,43 +1093,43 @@ async def toggle_like(
|
||||
raise HTTPException(status_code=500, detail=f"좋아요 처리에 실패했습니다: {str(e)}")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/share/{video_id}",
|
||||
response_class=HTMLResponse,
|
||||
summary="영상 공유용 Open Graph 페이지",
|
||||
description="영상별 제목, 설명, 포스터 메타데이터가 포함된 공개 HTML을 반환합니다.",
|
||||
responses={
|
||||
200: {"description": "공유 메타데이터 HTML 반환"},
|
||||
404: {"description": "공유 가능한 완료 영상을 찾을 수 없음"},
|
||||
},
|
||||
)
|
||||
async def get_video_share_page(
|
||||
video_id: int,
|
||||
request: Request,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> HTMLResponse:
|
||||
"""공개 공유 페이지를 반환하고 일반 브라우저는 영상 상세로 이동시킵니다."""
|
||||
share_data = await get_video_share_data(session, video_id)
|
||||
if share_data is None:
|
||||
raise HTTPException(status_code=404, detail="공유 가능한 영상을 찾을 수 없습니다.")
|
||||
|
||||
share_url = str(request.url).split("?", maxsplit=1)[0]
|
||||
html = build_video_share_html(
|
||||
share_data,
|
||||
share_url=share_url,
|
||||
frontend_base_url=prj_settings.SHARE_FRONTEND_URL,
|
||||
configured_default_image_url=prj_settings.SHARE_DEFAULT_IMAGE_URL,
|
||||
)
|
||||
return HTMLResponse(
|
||||
content=html,
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=300",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/share/{video_id}",
|
||||
response_class=HTMLResponse,
|
||||
summary="영상 공유용 Open Graph 페이지",
|
||||
description="영상별 제목, 설명, 포스터 메타데이터가 포함된 공개 HTML을 반환합니다.",
|
||||
responses={
|
||||
200: {"description": "공유 메타데이터 HTML 반환"},
|
||||
404: {"description": "공유 가능한 완료 영상을 찾을 수 없음"},
|
||||
},
|
||||
)
|
||||
async def get_video_share_page(
|
||||
video_id: int,
|
||||
request: Request,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> HTMLResponse:
|
||||
"""공개 공유 페이지를 반환하고 일반 브라우저는 영상 상세로 이동시킵니다."""
|
||||
share_data = await get_video_share_data(session, video_id)
|
||||
if share_data is None:
|
||||
raise HTTPException(status_code=404, detail="공유 가능한 영상을 찾을 수 없습니다.")
|
||||
|
||||
share_url = str(request.url).split("?", maxsplit=1)[0]
|
||||
html = build_video_share_html(
|
||||
share_data,
|
||||
share_url=share_url,
|
||||
frontend_base_url=prj_settings.SHARE_FRONTEND_URL,
|
||||
configured_default_image_url=prj_settings.SHARE_DEFAULT_IMAGE_URL,
|
||||
)
|
||||
return HTMLResponse(
|
||||
content=html,
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=300",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{video_id}",
|
||||
summary="단일 영상 상세 조회",
|
||||
|
||||
@ -12,6 +12,7 @@ from app.video.models import Video
|
||||
|
||||
FALLBACK_FRONTEND_URL = "https://ado2.o2osolution.ai"
|
||||
DEFAULT_SHARE_IMAGE_PATH = "/assets/images/ado2_image.png"
|
||||
DEFAULT_SHARE_IMAGE_STATIC_PATH = "/static/images/ado2_image.png"
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class VideoShareData:
|
||||
@ -68,6 +69,7 @@ def build_video_share_html(
|
||||
fallback_image_url = _resolve_default_image_url(
|
||||
configured_default_image_url,
|
||||
frontend_base,
|
||||
share_url=share_url,
|
||||
)
|
||||
image_url = _absolute_http_url(data.poster_url) or fallback_image_url
|
||||
|
||||
@ -136,14 +138,40 @@ def _normalise_frontend_base_url(value: str) -> str:
|
||||
return urlunsplit((parts.scheme, parts.netloc, path, "", ""))
|
||||
|
||||
|
||||
def _resolve_default_image_url(configured_url: str, frontend_base: str) -> str:
|
||||
"""설정된 기본 이미지 또는 프론트엔드의 공개 기본 이미지를 반환합니다."""
|
||||
def _resolve_default_image_url(
|
||||
configured_url: str,
|
||||
frontend_base: str,
|
||||
*,
|
||||
share_url: str = "",
|
||||
) -> str:
|
||||
"""포스터가 없을 때 사용할 기본 OG 이미지 URL을 반환합니다.
|
||||
|
||||
우선순위:
|
||||
1. ``SHARE_DEFAULT_IMAGE_URL`` (.env)
|
||||
2. 공유 API 호스트 ``/static/images/ado2_image.png``
|
||||
3. ``SHARE_FRONTEND_URL`` + ``/assets/images/ado2_image.png``
|
||||
"""
|
||||
configured_absolute_url = _absolute_http_url(configured_url)
|
||||
if configured_absolute_url:
|
||||
return configured_absolute_url
|
||||
|
||||
share_origin = _origin_from_url(share_url)
|
||||
if share_origin:
|
||||
return f"{share_origin}{DEFAULT_SHARE_IMAGE_STATIC_PATH}"
|
||||
|
||||
return f"{frontend_base}{DEFAULT_SHARE_IMAGE_PATH}"
|
||||
|
||||
|
||||
def _origin_from_url(value: str) -> str | None:
|
||||
"""URL에서 scheme + host(+port) origin만 추출합니다."""
|
||||
absolute_url = _absolute_http_url(value)
|
||||
if not absolute_url:
|
||||
return None
|
||||
|
||||
parts = urlsplit(absolute_url)
|
||||
return urlunsplit((parts.scheme, parts.netloc, "", "", ""))
|
||||
|
||||
|
||||
def _absolute_http_url(value: str | None) -> str | None:
|
||||
"""값이 절대 HTTP(S) URL인 경우에만 정리된 문자열을 반환합니다."""
|
||||
candidate = (value or "").strip()
|
||||
|
||||
@ -35,11 +35,14 @@ class ProjectSettings(BaseSettings):
|
||||
ADMIN_SESSION_MAX_AGE: int = Field(default=60 * 60 * 8)
|
||||
SHARE_FRONTEND_URL: str = Field(
|
||||
default="https://ado2.o2osolution.ai",
|
||||
description="영상 공유 링크가 이동할 프론트엔드 공개 기준 URL",
|
||||
description="공유 페이지에서 영상 상세로 이동할 프론트엔드 공개 기준 URL (.env: SHARE_FRONTEND_URL)",
|
||||
)
|
||||
SHARE_DEFAULT_IMAGE_URL: str = Field(
|
||||
default="https://ado2.o2osolution.ai/assets/images/ado2_image.png",
|
||||
description="포스터가 없는 영상 공유 시 사용할 절대 이미지 URL",
|
||||
default="",
|
||||
description=(
|
||||
"포스터가 없는 영상 공유 시 사용할 절대 이미지 URL (.env: SHARE_DEFAULT_IMAGE_URL). "
|
||||
"비우면 공유 API /static/images/ado2_image.png, 없으면 SHARE_FRONTEND_URL/assets 경로 사용"
|
||||
),
|
||||
)
|
||||
DEBUG: bool = Field(default=True)
|
||||
TIMEZONE: str = Field(
|
||||
|
||||
BIN
static/images/ado2_image.png
Normal file
BIN
static/images/ado2_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
Loading…
Reference in New Issue
Block a user