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 )