"""Catalog API routes — query and manage generated images.""" from __future__ import annotations from pathlib import Path from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse from content_engine.models.schemas import ImageResponse router = APIRouter(prefix="/api", tags=["catalog"]) _catalog = None def init_routes(catalog): """Initialize route dependencies.""" global _catalog _catalog = catalog @router.get("/images", response_model=list[ImageResponse]) async def list_images( character_id: str | None = None, content_rating: str | None = None, template_id: str | None = None, is_approved: bool | None = None, is_published: bool | None = None, pose: str | None = None, outfit: str | None = None, emotion: str | None = None, limit: int = 50, offset: int = 0, ): """Search and list generated images with optional filters.""" if _catalog is None: raise HTTPException(503, "Catalog not initialized") images = await _catalog.search( character_id=character_id, content_rating=content_rating, template_id=template_id, is_approved=is_approved, is_published=is_published, pose=pose, outfit=outfit, emotion=emotion, limit=limit, offset=offset, ) return [ ImageResponse( id=img.id, character_id=img.character_id, template_id=img.template_id, content_rating=img.content_rating, file_path=img.file_path, seed=img.seed, pose=img.pose, outfit=img.outfit, emotion=img.emotion, camera_angle=img.camera_angle, lighting=img.lighting, scene=img.scene, quality_score=img.quality_score, is_approved=img.is_approved, is_published=img.is_published, created_at=img.created_at, ) for img in images ] @router.get("/images/{image_id}", response_model=ImageResponse) async def get_image(image_id: str): """Get a single image by ID.""" if _catalog is None: raise HTTPException(503, "Catalog not initialized") img = await _catalog.get_image(image_id) if img is None: raise HTTPException(404, f"Image not found: {image_id}") return ImageResponse( id=img.id, character_id=img.character_id, template_id=img.template_id, content_rating=img.content_rating, file_path=img.file_path, seed=img.seed, pose=img.pose, outfit=img.outfit, emotion=img.emotion, camera_angle=img.camera_angle, lighting=img.lighting, scene=img.scene, quality_score=img.quality_score, is_approved=img.is_approved, is_published=img.is_published, created_at=img.created_at, ) @router.get("/images/{image_id}/file") async def serve_image_file(image_id: str): """Serve the actual image file for display in the UI.""" if _catalog is None: raise HTTPException(503, "Catalog not initialized") img = await _catalog.get_image(image_id) if img is None: raise HTTPException(404, f"Image not found: {image_id}") file_path = Path(img.file_path) if not file_path.exists(): raise HTTPException(404, f"Image file not found on disk") ext = file_path.suffix.lower() media_type = "video/mp4" if ext == ".mp4" else "video/webm" if ext == ".webm" else "image/png" return FileResponse( file_path, media_type=media_type, headers={"Cache-Control": "public, max-age=3600"}, ) @router.get("/images/{image_id}/download") async def download_image_file(image_id: str): """Download the image file as an attachment.""" if _catalog is None: raise HTTPException(503, "Catalog not initialized") img = await _catalog.get_image(image_id) if img is None: raise HTTPException(404, f"Image not found: {image_id}") file_path = Path(img.file_path) if not file_path.exists(): raise HTTPException(404, "Image file not found on disk") return FileResponse( file_path, media_type="image/png", filename=file_path.name, ) @router.post("/images/{image_id}/approve") async def approve_image(image_id: str): """Mark an image as approved for publishing.""" if _catalog is None: raise HTTPException(503, "Catalog not initialized") success = await _catalog.approve_image(image_id) if not success: raise HTTPException(404, f"Image not found: {image_id}") return {"status": "approved", "image_id": image_id} @router.delete("/images/{image_id}") async def delete_image(image_id: str): """Delete an image from the catalog and disk.""" if _catalog is None: raise HTTPException(503, "Catalog not initialized") success = await _catalog.delete_image(image_id) if not success: raise HTTPException(404, f"Image not found: {image_id}") return {"status": "deleted", "image_id": image_id}