Gallery-Archivist/backend/api/files/views.py

168 lines
5.5 KiB
Python

import os
from django.conf import settings
from django.http import FileResponse
from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse
from sorl.thumbnail import get_thumbnail
from apps.files.models import PostFileModel
from .serializers import (
PostFileSerializer,
FileResponseSerializer,
ErrorResponseSerializer,
)
THUMBNAIL_SIZES = {
"sx": (64, ".thumb_64.jpg"),
"sm": (256, ".thumb_256.jpg"),
"md": (748, ".thumb_748.jpg"),
"lg": (1024, ".thumb_1024.jpg"),
"xl": (2048, ".thumb_2048.jpg"),
}
class FileServeView(GenericAPIView):
"""
API view to serve content files for download or inline viewing.
Authentication can be provided via:
1. Authorization header (JWT token)
2. 'token' query parameter (JWT token)
"""
# Set permissions as needed
permission_classes = [IsAuthenticated]
serializer_class = FileResponseSerializer
queryset = PostFileModel.objects.all()
def get_thumbnail_file(self, source_path, size_key):
"""Generates and retrieves the thumbnail file."""
size, suffix = THUMBNAIL_SIZES.get(size_key, (None, ""))
if size:
thumbnail_file = get_thumbnail(source_path, str(size), upscale=False)
return os.path.abspath(
os.path.join(settings.MEDIA_ROOT, thumbnail_file.name)
), suffix
return None, ""
@extend_schema(
parameters=[
OpenApiParameter(
name="d",
description="Download flag (0 = download, otherwise inline)",
required=False,
type=str,
),
OpenApiParameter(
name="t",
description="Thumbnail size (sx, sm, md, lg, xl)",
required=False,
type=str,
),
OpenApiParameter(
name="token",
description="JWT token for authentication (alternative to Authorization header)",
required=False,
type=str,
),
],
responses={
200: OpenApiResponse(description="File returned successfully"),
401: ErrorResponseSerializer,
404: ErrorResponseSerializer,
500: ErrorResponseSerializer,
},
)
def get(self, request, file_hash):
"""Handle GET requests for file serving."""
download = request.query_params.get("d") == "0"
thumbnail_key = request.query_params.get("t")
try:
obj_file = PostFileModel.objects.filter(hash_blake3=file_hash).first()
if not obj_file:
return Response(
{"error": "File not found"}, status=status.HTTP_404_NOT_FOUND
)
file_name = obj_file.name.first().filename
file_type = obj_file.file_type
source_file = obj_file.file
# Use thumbnail if requested and file type is image
if thumbnail_key and file_type != "image":
source_file = obj_file.thumbnail
# Retrieve the requested thumbnail file if applicable
if thumbnail_key in THUMBNAIL_SIZES:
thumbnail_path, suffix = self.get_thumbnail_file(
source_file.path, thumbnail_key
)
if thumbnail_path:
file_name += suffix
file = open(thumbnail_path, "rb")
else:
file = source_file.file
else:
file = source_file.file
response = FileResponse(file)
disposition_type = "attachment" if download else "inline"
response["Content-Disposition"] = (
f'{disposition_type}; filename="{file_name}"'
)
return response
except Exception as e:
return Response(
{"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class FileDetailView(GenericAPIView):
"""
API view to get file metadata without serving the actual file.
Authentication can be provided via:
1. Authorization header (JWT token)
2. 'token' query parameter (JWT token)
"""
permission_classes = [IsAuthenticated]
serializer_class = PostFileSerializer
queryset = PostFileModel.objects.all()
@extend_schema(
parameters=[
OpenApiParameter(
name="token",
description="JWT token for authentication (alternative to Authorization header)",
required=False,
type=str,
)
],
responses={
200: PostFileSerializer,
401: ErrorResponseSerializer,
404: ErrorResponseSerializer,
500: ErrorResponseSerializer,
},
)
def get(self, request, file_hash):
"""Return file metadata."""
try:
obj_file = PostFileModel.objects.filter(hash_blake3=file_hash).first()
if not obj_file:
return Response(
{"error": "File not found"}, status=status.HTTP_404_NOT_FOUND
)
serializer = self.get_serializer(obj_file)
return Response(serializer.data)
except Exception as e:
return Response(
{"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)