diff --git a/core/views.py b/core/views.py index 9f90857..d8fddd4 100644 --- a/core/views.py +++ b/core/views.py @@ -1,9 +1,13 @@ +import json +from typing import ClassVar + import markdown_it -from django.http import JsonResponse +from django.http import HttpResponse from django.shortcuts import redirect from django.templatetags.static import static from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe +from django.views.decorators.cache import cache_control from django.views.generic import TemplateView, View from django.views.static import serve @@ -36,14 +40,50 @@ class About(TemplateView): } -class AppManifest(View): +class StaticContentView(View): + """ + A view that returns a bit of static content. + """ + + # Content type of the static payload + content_type: str + + # The static content that will be returned by the view + static_content: ClassVar[str | bytes] + + def __init__(self, **kwargs): + super().__init__(**kwargs) + if getattr(StaticContentView, "static_content", None) is None: + StaticContentView.static_content = self.get_static_content() + + def get(self, request, *args, **kwargs): + return HttpResponse( + StaticContentView.static_content, + content_type=self.content_type, + ) + + def get_static_content(self) -> str | bytes: + """ + Override to generate the view's static content. + """ + raise NotImplementedError() + + +@method_decorator(cache_control(max_age=60 * 15), name="dispatch") +class AppManifest(StaticContentView): """ Serves a PWA manifest file. This is a view as we want to drive some items from settings. + + NOTE: If this view changes to need runtime Config, it should change from + StaticContentView to View, otherwise the settings will only get + picked up during boot time. """ - def get(self, request): - return JsonResponse( + content_type = "application/json" + + def get_static_content(self) -> str | bytes: + return json.dumps( { "$schema": "https://json.schemastore.org/web-manifest-combined.json", "name": "Takahē", diff --git a/users/views/activitypub.py b/users/views/activitypub.py index c28fed3..c6d671f 100644 --- a/users/views/activitypub.py +++ b/users/views/activitypub.py @@ -4,6 +4,7 @@ from asgiref.sync import async_to_sync from django.conf import settings from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_control from django.views.decorators.csrf import csrf_exempt from django.views.generic import View @@ -18,6 +19,7 @@ from core.signatures import ( VerificationError, VerificationFormatError, ) +from core.views import StaticContentView from takahe import __version__ from users.models import Identity, InboxMessage, SystemActor from users.shortcuts import by_handle_or_404 @@ -216,35 +218,38 @@ class Outbox(View): ) -class EmptyOutbox(View): +@method_decorator(cache_control(max_age=60 * 15), name="dispatch") +class EmptyOutbox(StaticContentView): """ A fixed-empty outbox for the system actor """ - def get(self, request, *args, **kwargs): - return JsonResponse( + content_type: str = "application/activity+json" + + def get_static_content(self) -> str | bytes: + return json.dumps( canonicalise( { "type": "OrderedCollection", "totalItems": 0, "orderedItems": [], } - ), - content_type="application/activity+json", + ) ) -@method_decorator(cache_page(), name="dispatch") -class SystemActorView(View): +@method_decorator(cache_control(max_age=60 * 15), name="dispatch") +class SystemActorView(StaticContentView): """ Special endpoint for the overall system actor """ - def get(self, request): - return JsonResponse( + content_type: str = "application/activity+json" + + def get_static_content(self) -> str | bytes: + return json.dumps( canonicalise( SystemActor().to_ap(), include_security=True, - ), - content_type="application/activity+json", + ) )