166 lines
4.7 KiB
Python
166 lines
4.7 KiB
Python
import os
|
|
import uuid
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from fastapi import APIRouter, UploadFile, File, HTTPException, status, Depends
|
|
from fastapi.responses import FileResponse
|
|
from app.dependencies import get_current_user, get_optional_current_user
|
|
from app.models.user import User
|
|
|
|
router = APIRouter(prefix="/api/uploads", tags=["uploads"])
|
|
|
|
# Storage directory for uploaded images
|
|
STORAGE_DIR = Path("/home/shihaam/git/sarlink/mapmaker/storage/images")
|
|
STORAGE_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Allowed image types
|
|
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"}
|
|
ALLOWED_MIME_TYPES = {
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/gif",
|
|
"image/webp",
|
|
"image/bmp"
|
|
}
|
|
|
|
# Maximum file size: 8MB
|
|
MAX_FILE_SIZE = 8 * 1024 * 1024
|
|
|
|
|
|
@router.post("/image")
|
|
async def upload_image(
|
|
file: UploadFile = File(...),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Upload an image file.
|
|
Returns the file path that can be used to retrieve the image.
|
|
"""
|
|
# Validate file type by MIME type
|
|
if file.content_type not in ALLOWED_MIME_TYPES:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid file type. Allowed types: {', '.join(ALLOWED_MIME_TYPES)}"
|
|
)
|
|
|
|
# Read file content
|
|
content = await file.read()
|
|
|
|
# Validate file size
|
|
if len(content) > MAX_FILE_SIZE:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"File too large. Maximum size is 8MB."
|
|
)
|
|
|
|
# Get file extension
|
|
original_extension = Path(file.filename).suffix.lower()
|
|
if original_extension not in ALLOWED_EXTENSIONS:
|
|
# Fallback to MIME type mapping
|
|
mime_to_ext = {
|
|
"image/jpeg": ".jpg",
|
|
"image/png": ".png",
|
|
"image/gif": ".gif",
|
|
"image/webp": ".webp",
|
|
"image/bmp": ".bmp"
|
|
}
|
|
original_extension = mime_to_ext.get(file.content_type, ".jpg")
|
|
|
|
# Generate random filename
|
|
random_filename = f"{uuid.uuid4()}{original_extension}"
|
|
file_path = STORAGE_DIR / random_filename
|
|
|
|
# Save file
|
|
try:
|
|
with open(file_path, "wb") as f:
|
|
f.write(content)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to save file: {str(e)}"
|
|
)
|
|
|
|
# Return the relative path
|
|
return {
|
|
"filename": random_filename,
|
|
"path": f"/api/uploads/image/{random_filename}",
|
|
"size": len(content)
|
|
}
|
|
|
|
|
|
@router.get("/image/{filename}")
|
|
async def get_image(
|
|
filename: str,
|
|
current_user: Optional[User] = Depends(get_optional_current_user)
|
|
):
|
|
"""
|
|
Retrieve an uploaded image.
|
|
Public endpoint - requires either authenticated user or valid share token.
|
|
"""
|
|
# Validate filename to prevent directory traversal
|
|
if ".." in filename or "/" in filename or "\\" in filename:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid filename"
|
|
)
|
|
|
|
file_path = STORAGE_DIR / filename
|
|
|
|
# Check if file exists
|
|
if not file_path.exists() or not file_path.is_file():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Image not found"
|
|
)
|
|
|
|
# Determine media type based on extension
|
|
extension = file_path.suffix.lower()
|
|
media_type_map = {
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".png": "image/png",
|
|
".gif": "image/gif",
|
|
".webp": "image/webp",
|
|
".bmp": "image/bmp"
|
|
}
|
|
media_type = media_type_map.get(extension, "application/octet-stream")
|
|
|
|
return FileResponse(file_path, media_type=media_type)
|
|
|
|
|
|
@router.delete("/image/{filename}")
|
|
async def delete_image(
|
|
filename: str,
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Delete an uploaded image.
|
|
Requires authentication.
|
|
"""
|
|
# Validate filename to prevent directory traversal
|
|
if ".." in filename or "/" in filename or "\\" in filename:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid filename"
|
|
)
|
|
|
|
file_path = STORAGE_DIR / filename
|
|
|
|
# Check if file exists
|
|
if not file_path.exists() or not file_path.is_file():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Image not found"
|
|
)
|
|
|
|
# Delete file
|
|
try:
|
|
os.remove(file_path)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to delete file: {str(e)}"
|
|
)
|
|
|
|
return {"message": "Image deleted successfully"}
|