import logging, json from redis.asyncio import Redis from config import social_oauth_settings, db_settings from app.social.constants import YOUTUBE_SEO_HASH from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from app.social.schemas import ( YoutubeDescriptionRequest, YoutubeDescriptionResponse, ) from app.database.session import get_session from app.user.dependencies import get_current_user from app.user.models import User from app.home.models import Project, MarketingIntel from fastapi import APIRouter, BackgroundTasks, Depends, Query from fastapi import HTTPException, status from app.utils.prompts.prompts import yt_upload_prompt from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError redis_seo_client = Redis( host=db_settings.REDIS_HOST, port=db_settings.REDIS_PORT, db=0, decode_responses=True, ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/seo", tags=["Social SEO"]) @router.post( "/youtube", response_model=YoutubeDescriptionResponse, summary="유튜브 SEO descrption 생성", description="유튜브 업로드 시 사용할 descrption을 SEO 적용하여 생성", ) async def youtube_seo_description( request_body: YoutubeDescriptionRequest, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> YoutubeDescriptionResponse: # TODO : 나중에 Session Task_id 검증 미들웨어 만들면 추가해주세요. logger.info( f"[youtube_seo_description] Try Cache - user: {current_user.user_uuid} / task_id : {request_body.task_id}" ) cached = await get_yt_seo_in_redis(request_body.task_id) if cached: # redis hit return cached logger.info( f"[youtube_seo_description] Cache miss - user: {current_user.user_uuid} " ) updated_seo = await make_youtube_seo_description(request_body.task_id, current_user, session) await set_yt_seo_in_redis(request_body.task_id, updated_seo) return updated_seo async def make_youtube_seo_description( task_id: str, current_user: User, session: AsyncSession, ) -> YoutubeDescriptionResponse: logger.info( f"[make_youtube_seo_description] START - user: {current_user.user_uuid} " ) try: project_query = await session.execute( select(Project) .where( Project.task_id == task_id, Project.user_uuid == current_user.user_uuid) .order_by(Project.created_at.desc()) .limit(1) ) project = project_query.scalar_one_or_none() marketing_query = await session.execute( select(MarketingIntel) .where(MarketingIntel.id == project.marketing_inteligence) ) marketing_intelligence = marketing_query.scalar_one_or_none() hashtags = marketing_intelligence.intel_result["target_keywords"] yt_seo_input_data = { "customer_name" : project.store_name, "detail_region_info" : project.detail_region_info, "marketing_intelligence_summary" : json.dumps(marketing_intelligence.intel_result, ensure_ascii=False), "language" : project.language, "target_keywords" : hashtags } chatgpt = ChatgptService() yt_seo_output = await chatgpt.generate_structured_output(yt_upload_prompt, yt_seo_input_data) result_dict = { "title" : yt_seo_output.title, "description" : yt_seo_output.description, "keywords": hashtags } result = YoutubeDescriptionResponse(**result_dict) return result except Exception as e: logger.error(f"[youtube_seo_description] EXCEPTION - error: {e}") raise HTTPException( status_code=500, detail=f"유튜브 SEO 생성에 실패했습니다. : {str(e)}", ) async def get_yt_seo_in_redis(task_id:str) -> YoutubeDescriptionResponse | None: field = f"task_id:{task_id}" yt_seo_info = await redis_seo_client.hget(YOUTUBE_SEO_HASH, field) if yt_seo_info: yt_seo = json.loads(yt_seo_info) else: return None return YoutubeDescriptionResponse(**yt_seo) async def set_yt_seo_in_redis(task_id:str, yt_seo : YoutubeDescriptionResponse) -> None: field = f"task_id:{task_id}" yt_seo_info = json.dumps(yt_seo.model_dump(), ensure_ascii=False) await redis_seo_client.hsetex(YOUTUBE_SEO_HASH, field, yt_seo_info, ex=3600) return