""" API endpoints para publicación en redes sociales. """ from typing import Optional, List from fastapi import APIRouter, HTTPException from pydantic import BaseModel from app.publishers.manager import publisher_manager, Platform, MultiPublishResult router = APIRouter() # ============================================================ # Schemas # ============================================================ class PublishRequest(BaseModel): """Solicitud de publicación simple.""" platform: str content: str image_url: Optional[str] = None class MultiPublishRequest(BaseModel): """Solicitud de publicación múltiple.""" platforms: List[str] content: dict # {platform: content} o string único image_url: Optional[str] = None class ThreadPublishRequest(BaseModel): """Solicitud de publicación de hilo.""" platform: str posts: List[str] images: Optional[List[str]] = None class PublishResponse(BaseModel): """Respuesta de publicación.""" success: bool post_id: Optional[str] = None url: Optional[str] = None error: Optional[str] = None class ConnectionTestResponse(BaseModel): """Respuesta de test de conexión.""" platform: str configured: bool connected: bool error: Optional[str] = None details: dict = {} # ============================================================ # Endpoints # ============================================================ @router.post("/single", response_model=PublishResponse) async def publish_single(request: PublishRequest): """ Publicar contenido en una plataforma específica. - **platform**: x, threads, facebook, instagram - **content**: Texto del post - **image_url**: URL o ruta de imagen (opcional) """ try: platform = Platform(request.platform.lower()) except ValueError: raise HTTPException( status_code=400, detail=f"Plataforma no válida: {request.platform}. " f"Opciones: x, threads, facebook, instagram" ) result = await publisher_manager.publish( platform=platform, content=request.content, image_path=request.image_url ) return PublishResponse( success=result.success, post_id=result.post_id, url=result.url, error=result.error_message ) @router.post("/multiple") async def publish_multiple(request: MultiPublishRequest): """ Publicar contenido en múltiples plataformas. - **platforms**: Lista de plataformas ["x", "threads", "facebook"] - **content**: Dict con contenido por plataforma o string único - **image_url**: URL o ruta de imagen (opcional) """ platforms = [] for p in request.platforms: try: platforms.append(Platform(p.lower())) except ValueError: raise HTTPException( status_code=400, detail=f"Plataforma no válida: {p}" ) result = await publisher_manager.publish_to_multiple( platforms=platforms, content=request.content, image_path=request.image_url ) return { "success": result.success, "successful_platforms": result.successful_platforms, "failed_platforms": result.failed_platforms, "results": { p: { "success": r.success, "post_id": r.post_id, "url": r.url, "error": r.error_message } for p, r in result.results.items() }, "errors": result.errors } @router.post("/thread", response_model=PublishResponse) async def publish_thread(request: ThreadPublishRequest): """ Publicar un hilo de posts en una plataforma. - **platform**: x o threads - **posts**: Lista de textos para cada post del hilo - **images**: Lista de URLs de imágenes (opcional) """ try: platform = Platform(request.platform.lower()) except ValueError: raise HTTPException( status_code=400, detail=f"Plataforma no válida: {request.platform}" ) if platform not in [Platform.X, Platform.THREADS]: raise HTTPException( status_code=400, detail="Los hilos solo están soportados en X y Threads" ) result = await publisher_manager.publish_thread( platform=platform, posts=request.posts, images=request.images ) return PublishResponse( success=result.success, post_id=result.post_id, url=result.url, error=result.error_message ) @router.get("/test/{platform}", response_model=ConnectionTestResponse) async def test_connection(platform: str): """ Probar conexión con una plataforma específica. Verifica que las credenciales estén configuradas y funcionando. """ try: plat = Platform(platform.lower()) except ValueError: raise HTTPException( status_code=400, detail=f"Plataforma no válida: {platform}" ) result = await publisher_manager.test_connection(plat) return ConnectionTestResponse(**result) @router.get("/test") async def test_all_connections(): """ Probar conexión con todas las plataformas. Devuelve el estado de cada plataforma configurada. """ results = await publisher_manager.test_all_connections() return { "platforms": results, "summary": { "total": len(results), "configured": sum(1 for r in results.values() if r["configured"]), "connected": sum(1 for r in results.values() if r["connected"]) } } @router.get("/platforms") async def get_available_platforms(): """ Obtener lista de plataformas disponibles. Solo devuelve plataformas con credenciales configuradas. """ return { "available": publisher_manager.get_available_platforms(), "all": [p.value for p in Platform] } class PreviewRequest(BaseModel): """Solicitud de previsualización.""" content: str platforms: List[str] @router.post("/preview") async def preview_content(request: PreviewRequest): """ Previsualizar cómo se verá el contenido en cada plataforma. Valida longitud y muestra advertencias. """ previews = {} char_limits = { "x": 280, "threads": 500, "instagram": 2200, "facebook": 63206 } for p in request.platforms: limit = char_limits.get(p.lower(), 500) length = len(request.content) previews[p] = { "content": request.content[:limit], "length": length, "limit": limit, "valid": length <= limit, "truncated": length > limit, "remaining": max(0, limit - length) } return { "original_length": len(request.content), "previews": previews }