""" Discard API router """ import os import logging from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select, and_ from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models import Photo logger = logging.getLogger(__name__) router = APIRouter() @router.get("") async def list_discarded(db: AsyncSession = Depends(get_db)): """List discarded photos""" result = await db.execute( select(Photo).where(Photo.is_discarded == True) ) photos = result.scalars().all() return photos @router.post("/restore") async def restore_photos(photo_ids: list[str], db: AsyncSession = Depends(get_db)): """Restore photos from the discard pile""" result = await db.execute( select(Photo).where(and_(Photo.id.in_(photo_ids), Photo.is_discarded == True)) ) photos = result.scalars().all() for photo in photos: photo.is_discarded = False photo.discarded_at = None await db.commit() return {"status": "success", "restored": len(photos)} @router.delete("/empty") async def empty_discard(db: AsyncSession = Depends(get_db)): """Permanently delete all discarded photos and unlink their files from disk. Failures on individual files are logged but don't abort the batch. """ result = await db.execute( select(Photo).where(Photo.is_discarded == True) ) photos = result.scalars().all() deleted = 0 file_errors = 0 for photo in photos: try: if photo.filepath and os.path.exists(photo.filepath): os.unlink(photo.filepath) except OSError as e: file_errors += 1 logger.error(f"Failed to unlink {photo.filepath}: {e}") await db.delete(photo) deleted += 1 await db.commit() return { "status": "success", "deleted": deleted, "file_errors": file_errors, }