diff --git a/cccatalog-api/cccatalog/api/controllers/search_controller.py b/cccatalog-api/cccatalog/api/controllers/search_controller.py index b94f98ca..4951493d 100644 --- a/cccatalog-api/cccatalog/api/controllers/search_controller.py +++ b/cccatalog-api/cccatalog/api/controllers/search_controller.py @@ -6,10 +6,11 @@ from elasticsearch_dsl.query import Query from cccatalog import settings from django.core.cache import cache +from django.urls import reverse import cccatalog.api.models as models import logging as log from rest_framework import serializers -from cccatalog.settings import THUMBNAIL_PROXY_URL, PROXY_THUMBS +from cccatalog.settings import PROXY_THUMBS from cccatalog.api.utils.validate_images import validate_images from cccatalog.api.utils.dead_link_mask import get_query_mask, get_query_hash from itertools import accumulate @@ -21,7 +22,6 @@ DEAD_LINK_RATIO = 1 / 2 THUMBNAIL = 'thumbnail' URL = 'url' -THUMBNAIL_WIDTH_PX = 600 PROVIDER = 'provider' DEEP_PAGINATION_ERROR = 'Deep pagination is not allowed.' QUERY_SPECIAL_CHARACTER_ERROR = 'Unescaped special characters are not allowed.' @@ -128,10 +128,12 @@ def _post_process_results(s, start, end, page_size, search_results, else: to_proxy = URL original = res[to_proxy] - proxied = '{proxy_url}/{width}/{original}'.format( - proxy_url=THUMBNAIL_PROXY_URL, - width=THUMBNAIL_WIDTH_PX, - original=original + ext = res["url"].split(".")[-1] + proxied = "http://{}{}".format( + request.get_host(), + reverse('thumbs', kwargs={ + 'identifier': "{}.{}".format(res["identifier"], ext) + }) ) res[THUMBNAIL] = proxied results.append(res) diff --git a/cccatalog-api/cccatalog/api/views/site_views.py b/cccatalog-api/cccatalog/api/views/site_views.py index d4ad8fd5..29fe4f08 100644 --- a/cccatalog-api/cccatalog/api/views/site_views.py +++ b/cccatalog-api/cccatalog/api/views/site_views.py @@ -1,6 +1,7 @@ import logging as log import secrets import smtplib +from urllib.request import urlopen from django.core.mail import send_mail from rest_framework.response import Response from rest_framework.reverse import reverse @@ -10,11 +11,13 @@ from cccatalog.api.serializers.oauth2_serializers import\ OAuth2RegistrationSerializer, OAuth2RegistrationSuccessful, OAuth2KeyInfo from drf_yasg.utils import swagger_auto_schema -from cccatalog.api.models import ContentProvider +from cccatalog.api.models import ContentProvider, Image from cccatalog.api.models import ThrottledApplication, OAuth2Verification from cccatalog.api.utils.throttle import TenPerDay, OnePerSecond from cccatalog.api.utils.oauth2_helper import get_token_info +from cccatalog.settings import THUMBNAIL_PROXY_URL, THUMBNAIL_WIDTH_PX from django.core.cache import cache +from django.http import HttpResponse IDENTIFIER = 'provider_identifier' NAME = 'provider_name' @@ -285,3 +288,50 @@ def get(self, request, format=None): 'verified': verified } return Response(status=200, data=response_data) + + +class Thumbs(APIView): + """ + Return the thumb of an image. + """ + + lookup_field = 'identifier' + queryset = Image.objects.all() + + @swagger_auto_schema(operation_id="thumb_lookup", + responses={ + 200: 'The thumb of an image', + 400: 'Bad Request', + 404: 'Not Found' + }) + def get(self, request, identifier, format=None): + path_element = identifier.split(".") + identifier = path_element[0] + extname = "" + if len(path_element) == 2: + extname = path_element[1] + elif len(path_element) > 2: + return Response(status=400) + try: + image = Image.objects.get(identifier=identifier) + if extname and image.url.split(".")[-1] != extname: + return Response(status=404, data='Not Found') + except Image.DoesNotExist: + return Response(status=404, data='Not Found') + + upstream_url = '{proxy_url}/{width}/{original}'.format( + proxy_url=THUMBNAIL_PROXY_URL, + width=THUMBNAIL_WIDTH_PX, + original=image.url + ) + upstream_response = urlopen(upstream_url) + status = upstream_response.status + content_type = upstream_response.headers.get('Content-Type') + + response = HttpResponse( + upstream_response.read(), + status=status, + content_type=content_type + ) + + return response diff --git a/cccatalog-api/cccatalog/settings.py b/cccatalog-api/cccatalog/settings.py index 0f27cd43..4033779c 100644 --- a/cccatalog-api/cccatalog/settings.py +++ b/cccatalog-api/cccatalog/settings.py @@ -156,11 +156,13 @@ } # Produce CC-hosted thumbnails dynamically through a proxy. -PROXY_THUMBS = bool(os.environ.get('PROXY_THUMBS', False)) +PROXY_THUMBS = bool(os.environ.get('PROXY_THUMBS', True)) THUMBNAIL_PROXY_URL = os.environ.get( - 'THUMBNAIL_PROXY_URL', 'https://localhost:8222' + 'THUMBNAIL_PROXY_URL', 'http://localhost:8222' ) +THUMBNAIL_WIDTH_PX = 600 + AUTHENTICATION_BACKENDS = ( 'oauth2_provider.backends.OAuth2Backend', 'django.contrib.auth.backends.ModelBackend', diff --git a/cccatalog-api/cccatalog/urls.py b/cccatalog-api/cccatalog/urls.py index 1268a21b..ccd409df 100644 --- a/cccatalog-api/cccatalog/urls.py +++ b/cccatalog-api/cccatalog/urls.py @@ -20,7 +20,7 @@ from cccatalog.api.views.image_views import SearchImages, ImageDetail,\ Watermark, RelatedImage, OembedView, ReportImageView from cccatalog.api.views.site_views import HealthCheck, ImageStats, Register, \ - CheckRates, VerifyEmail + CheckRates, VerifyEmail, Thumbs from cccatalog.api.views.link_views import CreateShortenedLink, \ ResolveShortenedLink from cccatalog.settings import API_VERSION, WATERMARK_ENABLED @@ -110,6 +110,7 @@ ), path('link', CreateShortenedLink.as_view(), name='make-link'), path('link/', ResolveShortenedLink.as_view(), name='resolve'), + path('thumbs/', Thumbs.as_view(), name='thumbs'), path('oembed', OembedView.as_view(), name='oembed') ] if WATERMARK_ENABLED: diff --git a/cccatalog-api/test/v1_integration_test.py b/cccatalog-api/test/v1_integration_test.py index 813d1af1..2bc72681 100644 --- a/cccatalog-api/test/v1_integration_test.py +++ b/cccatalog-api/test/v1_integration_test.py @@ -108,6 +108,12 @@ def test_image_delete(search_fixture): deleted_response = requests.get(f'{API_URL}/image/{test_id}') assert deleted_response.status_code == 404 +@pytest.fixture +def test_image_thumb(search_fixture): + thumbnail_url = search_fixture['results'][0]['thumbnail'] + thumbnail_response = requests.get(thumbnail_url) + assert thumbnail_response.status_code == 200 + assert thumbnail_response.headers["Content-Type"].startswith("image/") @pytest.fixture def link_shortener_fixture(search_fixture): diff --git a/docker-compose.yml b/docker-compose.yml index 173513b7..082de88f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,6 +74,7 @@ services: ELASTICSEARCH_PORT: "9200" DISABLE_GLOBAL_THROTTLING: "True" ROOT_SHORTENING_URL: "localhost:8000" + THUMBNAIL_PROXY_URL: "http://thumbs:8222" stdin_open: true tty: true