ppt-tool/backend/utils/export_utils.py
Vadym Samoilenko ff9cdffc32 Phase 5: Fix export, slide edit, static files; add README
- Fix PPTX/PDF export: Puppeteer URL port mismatch (80 → 3000)
- Fix backend export_utils to use NEXT_INTERNAL_URL env var
- Add Chromium to frontend Dockerfile for Docker-based export
- Fix slide edit socket hang up with asyncio.wait_for() timeouts
- Add FastAPI StaticFiles mounts for /static and /app_data
- Add Next.js rewrite for /static/ to proxy to backend
- Show template thumbnail in master decks admin page
- Add error logging to ReviewWorkflow component
- Add Docker env vars for web service (APP_DATA_DIRECTORY, app_data volume)
- Add project README in English

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:40:36 +00:00

92 lines
3.4 KiB
Python

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 models.pptx_models import PptxPresentationModel
from models.presentation_and_path import PresentationAndPath
from services.pptx_presentation_creator import PptxPresentationCreator
from services.temp_file_service import TEMP_FILE_SERVICE
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":
# Get the converted PPTX model from the Next.js service
async with aiohttp.ClientSession() as http:
async with http.get(
f"{next_url}/api/presentation_to_pptx_model?id={presentation_id}"
) as response:
if response.status != 200:
error_text = await response.text()
print(f"Failed to get PPTX model: {error_text}")
raise HTTPException(
status_code=500,
detail="Failed to convert presentation to PPTX model",
)
pptx_model_data = await response.json()
# Create PPTX file using the converted model
pptx_model = PptxPresentationModel(**pptx_model_data)
# Apply brand enforcement if client has brand config
if client_id and session:
try:
from services.brand_enforcement_service import BrandEnforcementService
from models.sql.brand_config import BrandConfigModel
from sqlalchemy import select as sa_select
stmt = sa_select(BrandConfigModel).where(
BrandConfigModel.client_id == client_id
)
result = await session.execute(stmt)
brand = result.scalar_one_or_none()
if brand:
enforcer = BrandEnforcementService()
pptx_model = enforcer.enforce_on_pptx_model(pptx_model, brand)
except Exception as e:
print(f"Brand enforcement skipped: {e}")
temp_dir = TEMP_FILE_SERVICE.create_temp_dir()
pptx_creator = PptxPresentationCreator(pptx_model, temp_dir)
await pptx_creator.create_ppt()
export_directory = get_exports_directory()
pptx_path = os.path.join(
export_directory,
f"{sanitize_filename(title or str(uuid.uuid4()))}.pptx",
)
pptx_creator.save(pptx_path)
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"],
)