diff --git a/backend/api/files/__init__.py b/backend/api/files/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/files/serializers.py b/backend/api/files/serializers.py new file mode 100644 index 0000000..48bc0c8 --- /dev/null +++ b/backend/api/files/serializers.py @@ -0,0 +1,32 @@ +from rest_framework import serializers +from apps.files.models import PostFileModel + + +class PostFileSerializer(serializers.ModelSerializer): + """Serializer for PostFileModel.""" + + class Meta: + model = PostFileModel + fields = ["hash_blake3", "file_type", "file", "thumbnail"] + # Add any other fields you need + + def to_representation(self, instance): + """Customize the representation of the model.""" + representation = super().to_representation(instance) + + # Add file name from related model + try: + representation["filename"] = instance.name.first().filename + except (AttributeError, IndexError): + representation["filename"] = "Unknown" + + # Add URLs for different thumbnail sizes + base_url = f"/api/files/{instance.hash_blake3}/" + thumbnails = {} + for size_key in THUMBNAIL_SIZES: + thumbnails[size_key] = f"{base_url}?t={size_key}" + + representation["thumbnails"] = thumbnails + representation["download_url"] = f"{base_url}?d=0" + + return representation diff --git a/backend/api/files/urls.py b/backend/api/files/urls.py new file mode 100644 index 0000000..67b40f5 --- /dev/null +++ b/backend/api/files/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from .views import FileServeView, FileDetailView + +urlpatterns = [ + # Serve the actual file + path("files//", FileServeView.as_view(), name="serve_file"), + # Get file metadata + path("files//info/", FileDetailView.as_view(), name="file_info"), +] diff --git a/backend/api/files/views.py b/backend/api/files/views.py new file mode 100644 index 0000000..16bb908 --- /dev/null +++ b/backend/api/files/views.py @@ -0,0 +1,108 @@ +import os +from django.conf import settings +from django.http import FileResponse +from rest_framework import status +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from sorl.thumbnail import get_thumbnail +from apps.files.models import PostFileModel +from .serializers import PostFileSerializer # You'll need to create this + +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(APIView): + """ + API view to serve content files for download or inline viewing. + """ + + # Uncomment the following line if authentication is required + # permission_classes = [IsAuthenticated] + + 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, "" + + 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(APIView): + """ + API view to get file metadata without serving the actual file. + """ + + # Uncomment the following line if authentication is required + # permission_classes = [IsAuthenticated] + + 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 = PostFileSerializer(obj_file) + return Response(serializer.data) + + except Exception as e: + return Response( + {"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) diff --git a/backend/api/urls.py b/backend/api/urls.py index 6e5a1c7..643ee5b 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -1,6 +1,5 @@ from django.urls import path, include -from apps.files.views import serve_content_file urlpatterns = [ path("schema/", include("api.schema.urls")), @@ -8,5 +7,5 @@ urlpatterns = [ path("user/", include("api.user.urls")), path("posts/", include("api.posts.urls")), path("creators/", include("api.creators.urls")), - path("files//", serve_content_file, name="serve_file"), + path("files/", include("api.files.urls")), ] diff --git a/backend/apps/files/views.py b/backend/apps/files/views.py deleted file mode 100644 index 62317ca..0000000 --- a/backend/apps/files/views.py +++ /dev/null @@ -1,69 +0,0 @@ -import os - -from django.conf import settings -from django.shortcuts import get_object_or_404 -from django.http import HttpResponse, FileResponse - -from sorl.thumbnail import get_thumbnail - -from .models import PostFileModel - - -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"), -} - - -def get_thumbnail_file(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, "" - - -def serve_content_file(request, file_hash): - """ - View function to serve content files for download or inline viewing. - """ - - download = request.GET.get("d") == "0" - thumbnail_key = request.GET.get("t") - - try: - obj_file = get_object_or_404(PostFileModel, hash_blake3=file_hash) - 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 = 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: - print(f"Error serving file: {e}") - return HttpResponse("File not found", status=404)