import os import uuid from typing import Literal, Optional import aiohttp from fastapi import HTTPException from pathvalidate import sanitize_filename from sqlalchemy.ext.asyncio import AsyncSession from sqlmodel import select from models.presentation_and_path import PresentationAndPath from models.sql.presentation import PresentationModel from models.sql.presentation_layout_code import PresentationLayoutCodeModel from models.sql.slide import SlideModel from services.database import async_session_maker from utils.asset_directory_utils import get_exports_directory async def export_presentation( presentation_id: uuid.UUID, title: str, export_as: Literal["pptx", "pdf"], client_id: Optional[uuid.UUID] = None, session: Optional[AsyncSession] = None, ) -> PresentationAndPath: next_url = os.environ.get("NEXT_INTERNAL_URL", "http://localhost:3000") if export_as == "pptx": # Query presentation, slides, and layout codes from the DB async with async_session_maker() as db: presentation = await db.get(PresentationModel, presentation_id) slides_result = await db.scalars( select(SlideModel) .where( SlideModel.presentation == presentation_id, SlideModel.deleted_at.is_(None), ) .order_by(SlideModel.index) ) slides = list(slides_result.all()) # Fetch layout codes if the presentation uses a custom template template_id_str = ( getattr(presentation, "template_name", None) if presentation else None ) layouts = [] if template_id_str: try: template_uuid = uuid.UUID(template_id_str) layouts_result = await db.scalars( select(PresentationLayoutCodeModel).where( PresentationLayoutCodeModel.presentation == template_uuid ) ) layouts = list(layouts_result.all()) except (ValueError, TypeError): pass # Build JSON payload for the Next.js PptxGenJS endpoint payload = { "title": title, "slides": [ { "id": str(s.id), "layout": s.layout, "layout_group": s.layout_group, "index": s.index, "content": s.content or {}, "speaker_note": s.speaker_note, } for s in slides ], "layouts": [ { "layout_id": lay.layout_id, "layout_name": lay.layout_name, "layout_code": lay.layout_code, } for lay in layouts ], } export_directory = get_exports_directory() pptx_path = os.path.join( export_directory, f"{sanitize_filename(title or str(uuid.uuid4()))}.pptx", ) async with aiohttp.ClientSession() as http: async with http.post( f"{next_url}/api/generate-pptx", json=payload, ) as response: if response.status != 200: error_text = await response.text() print(f"[export_utils] generate-pptx failed ({response.status}): {error_text}") raise HTTPException( status_code=500, detail="Failed to generate PPTX", ) pptx_bytes = await response.read() os.makedirs(export_directory, exist_ok=True) with open(pptx_path, "wb") as f: f.write(pptx_bytes) return PresentationAndPath( presentation_id=presentation_id, path=pptx_path, ) else: async with aiohttp.ClientSession() as http: async with http.post( f"{next_url}/api/export-as-pdf", json={ "id": str(presentation_id), "title": sanitize_filename(title or str(uuid.uuid4())), }, ) as response: response_json = await response.json() return PresentationAndPath( presentation_id=presentation_id, path=response_json["path"], )