ppt-tool/backend/api/v1/ppt/endpoints/slide.py
Vadym Samoilenko 864278a0fa Comprehensive audit: fix auth, basePath, security, and UI bugs
Backend security (P0):
- Add get_current_user auth to all files endpoints (upload, decompose, url, update)
- Add get_current_user auth to all images endpoints (generate, upload, uploaded, generated, delete)
- Add get_current_user auth to slide edit and edit-html endpoints
- Add get_current_user auth to outlines SSE stream endpoint (was fully unauthenticated)

Frontend API fixes:
- adminSlice fetchTeams: bare fetch() → apiFetch() (was missing basePath prefix)
- dashboard getPresentation: add missing getHeader() auth headers
- images getUploadedImages/deleteImage: add missing getHeader() auth headers
- templates/[id] toggle layout: bare fetch() → apiFetch() (404 in production)
- header.ts: remove incorrect client-side CORS headers (Access-Control-Allow-*)

UI fixes:
- admin/users: add fetchUsers() refetch after deactivate (table wasn't updating)
- presentationGeneration.ts: fix corrupt comment with embedded import statement

Security:
- has-required-key/route.ts: remove console.log() leaking OPENAI_API_KEY to logs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 18:46:45 +00:00

118 lines
4.1 KiB
Python

import asyncio
import logging
import os
from typing import Annotated, Optional
from fastapi import APIRouter, Body, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
import uuid
from models.sql.presentation import PresentationModel
from models.sql.slide import SlideModel
from models.sql.user import UserModel
from services.database import get_async_session
from services.image_generation_service import ImageGenerationService
from utils.auth_dependencies import get_current_user
from utils.asset_directory_utils import get_images_directory
from utils.llm_calls.edit_slide import get_edited_slide_content
from utils.llm_calls.edit_slide_html import get_edited_slide_html
from utils.llm_calls.select_slide_type_on_edit import get_slide_layout_from_prompt
from utils.process_slides import process_old_and_new_slides_and_fetch_assets
SLIDE_ROUTER = APIRouter(prefix="/slide", tags=["Slide"])
logger = logging.getLogger(__name__)
@SLIDE_ROUTER.post("/edit")
async def edit_slide(
id: Annotated[uuid.UUID, Body()],
prompt: Annotated[str, Body()],
sql_session: AsyncSession = Depends(get_async_session),
_current_user: UserModel = Depends(get_current_user),
):
slide = await sql_session.get(SlideModel, id)
if not slide:
raise HTTPException(status_code=404, detail="Slide not found")
presentation = await sql_session.get(PresentationModel, slide.presentation)
if not presentation:
raise HTTPException(status_code=404, detail="Presentation not found")
try:
presentation_layout = presentation.get_layout()
slide_layout = await asyncio.wait_for(
get_slide_layout_from_prompt(prompt, presentation_layout, slide),
timeout=int(os.getenv("LLM_TIMEOUT_SECONDS", "60")),
)
edited_slide_content = await asyncio.wait_for(
get_edited_slide_content(
prompt, slide, presentation.language, slide_layout
),
timeout=int(os.getenv("LLM_TIMEOUT_SECONDS", "90")),
)
image_generation_service = ImageGenerationService(get_images_directory())
# This will mutate edited_slide_content
new_assets = await asyncio.wait_for(
process_old_and_new_slides_and_fetch_assets(
image_generation_service,
slide.content,
edited_slide_content,
),
timeout=int(os.getenv("IMAGE_GENERATION_TIMEOUT_SECONDS", "120")),
)
except asyncio.TimeoutError:
raise HTTPException(
status_code=504,
detail="Slide editing timed out. The AI model or image generation took too long. Please try again.",
)
except HTTPException:
raise
except Exception as e:
logger.exception("Failed to edit slide:")
raise HTTPException(
status_code=500,
detail=f"Failed to edit slide: {str(e)}",
)
# Always assign a new unique id to the slide
slide.id = uuid.uuid4()
sql_session.add(slide)
slide.content = edited_slide_content
slide.layout = slide_layout.id
slide.speaker_note = edited_slide_content.get("__speaker_note__", "")
sql_session.add_all(new_assets)
await sql_session.commit()
return slide
@SLIDE_ROUTER.post("/edit-html", response_model=SlideModel)
async def edit_slide_html(
id: Annotated[uuid.UUID, Body()],
prompt: Annotated[str, Body()],
html: Annotated[Optional[str], Body()] = None,
sql_session: AsyncSession = Depends(get_async_session),
_current_user: UserModel = Depends(get_current_user),
):
slide = await sql_session.get(SlideModel, id)
if not slide:
raise HTTPException(status_code=404, detail="Slide not found")
html_to_edit = html or slide.html_content
if not html_to_edit:
raise HTTPException(status_code=400, detail="No HTML to edit")
edited_slide_html = await get_edited_slide_html(prompt, html_to_edit)
# Always assign a new unique id to the slide
# This is to ensure that the nextjs can track slide updates
slide.id = uuid.uuid4()
sql_session.add(slide)
slide.html_content = edited_slide_html
await sql_session.commit()
return slide