All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 11s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 8s
CI/CD Pipeline / VM Test - performance (push) Successful in 8s
CI/CD Pipeline / VM Test - security (push) Successful in 8s
CI/CD Pipeline / Backend Linting (push) Successful in 3s
CI/CD Pipeline / Frontend Linting (push) Successful in 18s
CI/CD Pipeline / Nix Flake Check (push) Successful in 43s
CI/CD Pipeline / CI Summary (push) Successful in 0s
129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
"""Export API endpoints for downloading and exporting images."""
|
|
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from fastapi.responses import StreamingResponse
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.deps import get_current_user, get_db
|
|
from app.database.models.board import Board
|
|
from app.database.models.board_image import BoardImage
|
|
from app.database.models.image import Image
|
|
from app.database.models.user import User
|
|
from app.images.download import download_single_image
|
|
from app.images.export_composite import create_composite_export
|
|
from app.images.export_zip import create_zip_export
|
|
|
|
router = APIRouter(tags=["export"])
|
|
|
|
|
|
@router.get("/images/{image_id}/download")
|
|
async def download_image(
|
|
image_id: UUID,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> StreamingResponse:
|
|
"""
|
|
Download a single image.
|
|
|
|
Only the image owner can download it.
|
|
"""
|
|
# Verify image exists and user owns it
|
|
image = db.query(Image).filter(Image.id == image_id, Image.user_id == current_user.id).first()
|
|
|
|
if image is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Image not found or access denied",
|
|
)
|
|
|
|
return await download_single_image(image.storage_path, image.filename)
|
|
|
|
|
|
@router.get("/boards/{board_id}/export/zip")
|
|
def export_board_zip(
|
|
board_id: UUID,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> StreamingResponse:
|
|
"""
|
|
Export all images from a board as a ZIP file.
|
|
|
|
Only the board owner can export it.
|
|
"""
|
|
# Verify board exists and user owns it
|
|
board = db.query(Board).filter(Board.id == board_id, Board.user_id == current_user.id).first()
|
|
|
|
if board is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Board not found or access denied",
|
|
)
|
|
|
|
return create_zip_export(str(board_id), db)
|
|
|
|
|
|
@router.get("/boards/{board_id}/export/composite")
|
|
def export_board_composite(
|
|
board_id: UUID,
|
|
scale: float = Query(1.0, ge=0.5, le=4.0, description="Resolution scale (0.5x to 4x)"),
|
|
format: str = Query("PNG", regex="^(PNG|JPEG)$", description="Output format (PNG or JPEG)"),
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> StreamingResponse:
|
|
"""
|
|
Export board as a single composite image showing the layout.
|
|
|
|
Only the board owner can export it.
|
|
|
|
Args:
|
|
scale: Resolution multiplier (0.5x, 1x, 2x, 4x)
|
|
format: Output format (PNG or JPEG)
|
|
"""
|
|
# Verify board exists and user owns it
|
|
board = db.query(Board).filter(Board.id == board_id, Board.user_id == current_user.id).first()
|
|
|
|
if board is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Board not found or access denied",
|
|
)
|
|
|
|
return create_composite_export(str(board_id), db, scale=scale, format=format)
|
|
|
|
|
|
@router.get("/boards/{board_id}/export/info")
|
|
def get_export_info(
|
|
board_id: UUID,
|
|
current_user: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> dict:
|
|
"""
|
|
Get information about board export (image count, estimated size).
|
|
|
|
Useful for showing progress estimates.
|
|
"""
|
|
# Verify board exists and user owns it
|
|
board = db.query(Board).filter(Board.id == board_id, Board.user_id == current_user.id).first()
|
|
|
|
if board is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Board not found or access denied",
|
|
)
|
|
|
|
# Count images and calculate estimated size
|
|
images = (
|
|
db.query(Image).join(BoardImage, BoardImage.image_id == Image.id).filter(BoardImage.board_id == board_id).all()
|
|
)
|
|
|
|
total_size = sum(img.file_size for img in images)
|
|
|
|
return {
|
|
"board_id": str(board_id),
|
|
"image_count": len(images),
|
|
"total_size_bytes": total_size,
|
|
"estimated_zip_size_bytes": int(total_size * 0.95), # ZIP usually has small overhead
|
|
}
|