diff --git a/backend/api/v1/admin/storage_router.py b/backend/api/v1/admin/storage_router.py index 472ed96..a47e8e9 100644 --- a/backend/api/v1/admin/storage_router.py +++ b/backend/api/v1/admin/storage_router.py @@ -12,6 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from models.sql.client import ClientModel from models.sql.master_deck import MasterDeckModel from models.sql.presentation import PresentationModel +from models.sql.slide import SlideModel from models.sql.user import UserModel from services import audit_service from services.access_service import get_accessible_client_ids @@ -361,7 +362,7 @@ async def purge_deleted_storage( admin: UserModel = Depends(require_client_admin), session: AsyncSession = Depends(get_async_session), ): - """Hard-delete files for soft-deleted presentations.""" + """Hard-delete files for soft-deleted presentations (exports + generated images).""" if admin.role != "super_admin": raise HTTPException(status_code=403, detail="Super admin only") @@ -376,8 +377,10 @@ async def purge_deleted_storage( purged_files = 0 purged_bytes = 0 purged_presentations = 0 + purged_images = 0 for p in deleted_presentations: + # Delete export files (PDF/PPTX) if p.file_paths: for path in p.file_paths: if path and os.path.isfile(path): @@ -389,6 +392,30 @@ async def purge_deleted_storage( except OSError: pass p.file_paths = [] + + # Delete generated images from slides + slides_stmt = select(SlideModel).where(SlideModel.presentation == p.id) + slides_result = await session.execute(slides_stmt) + slides = slides_result.scalars().all() + + for slide in slides: + if slide.content and isinstance(slide.content, dict): + # Extract image path from content.image.__image_url__ + image_data = slide.content.get("image") + if image_data and isinstance(image_data, dict): + image_url = image_data.get("__image_url__") + if image_url and image_url.startswith("/app_data/images/"): + # Convert URL to filesystem path + image_path = image_url.lstrip("/") + if os.path.isfile(image_path): + try: + size = os.path.getsize(image_path) + os.remove(image_path) + purged_images += 1 + purged_bytes += size + except OSError: + pass + purged_presentations += 1 await session.commit() @@ -397,12 +424,18 @@ async def purge_deleted_storage( user_id=admin.id, action="admin_purge", resource_type="storage", - details={"presentations": purged_presentations, "files": purged_files, "bytes": purged_bytes}, + details={ + "presentations": purged_presentations, + "files": purged_files, + "images": purged_images, + "bytes": purged_bytes + }, ) return { "ok": True, "purged_presentations": purged_presentations, "purged_files": purged_files, + "purged_images": purged_images, "purged_bytes": purged_bytes, } diff --git a/backend/services/image_generation_service.py b/backend/services/image_generation_service.py index ca8fb83..7f540aa 100644 --- a/backend/services/image_generation_service.py +++ b/backend/services/image_generation_service.py @@ -201,9 +201,9 @@ class ImageGenerationService: async def generate_image_nanobanana_pro( self, prompt: str, output_directory: str ) -> str: - """Generate image using NanoBanana Pro (gemini-3-pro-image-preview).""" + """Generate image using NanoBanana Pro (gemini-2.0-flash-exp-image).""" return await self._generate_image_google( - prompt, output_directory, "gemini-3-pro-image-preview" + prompt, output_directory, "gemini-2.0-flash-exp-image" ) async def get_image_from_pexels(self, prompt: str) -> str: