Add REST endpoint for uploading snapshots from the Singlefile extension (#996)

* Extract asset logic

* Allow disabling HTML snapshot when creating bookmark

* Add endpoint for uploading singlefile snapshots

* Add URL parameter to disable HTML snapshots

* Allow using asset list in base Docker image

* Expose app version through profile
This commit is contained in:
Sascha Ißbrücker
2025-02-23 22:58:14 +01:00
committed by GitHub
parent 2e97b13bad
commit 2d81ea6f6e
18 changed files with 723 additions and 314 deletions

View File

@@ -13,13 +13,7 @@ from bookmarks.api.serializers import (
UserProfileSerializer,
)
from bookmarks.models import Bookmark, BookmarkSearch, Tag, User
from bookmarks.services import auto_tagging
from bookmarks.services.bookmarks import (
archive_bookmark,
unarchive_bookmark,
website_loader,
)
from bookmarks.services.website_loader import WebsiteMetadata
from bookmarks.services import assets, bookmarks, auto_tagging, website_loader
logger = logging.getLogger(__name__)
@@ -57,10 +51,12 @@ class BookmarkViewSet(
def get_serializer_context(self):
disable_scraping = "disable_scraping" in self.request.GET
disable_html_snapshot = "disable_html_snapshot" in self.request.GET
return {
"request": self.request,
"user": self.request.user,
"disable_scraping": disable_scraping,
"disable_html_snapshot": disable_html_snapshot,
}
@action(methods=["get"], detail=False)
@@ -89,13 +85,13 @@ class BookmarkViewSet(
@action(methods=["post"], detail=True)
def archive(self, request, pk):
bookmark = self.get_object()
archive_bookmark(bookmark)
bookmarks.archive_bookmark(bookmark)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(methods=["post"], detail=True)
def unarchive(self, request, pk):
bookmark = self.get_object()
unarchive_bookmark(bookmark)
bookmarks.unarchive_bookmark(bookmark)
return Response(status=status.HTTP_204_NO_CONTENT)
@action(methods=["get"], detail=False)
@@ -129,6 +125,33 @@ class BookmarkViewSet(
status=status.HTTP_200_OK,
)
@action(methods=["post"], detail=False)
def singlefile(self, request):
url = request.data.get("url")
file = request.FILES.get("file")
if not url or not file:
return Response(
{"error": "Both 'url' and 'file' parameters are required."},
status=status.HTTP_400_BAD_REQUEST,
)
bookmark = Bookmark.objects.filter(owner=request.user, url=url).first()
if not bookmark:
bookmark = Bookmark(url=url)
bookmark = bookmarks.create_bookmark(
bookmark, "", request.user, disable_html_snapshot=True
)
bookmarks.enhance_with_website_metadata(bookmark)
assets.upload_snapshot(bookmark, file.read())
return Response(
{"message": "Snapshot uploaded successfully."},
status=status.HTTP_201_CREATED,
)
class TagViewSet(
viewsets.GenericViewSet,

View File

@@ -4,13 +4,10 @@ from rest_framework import serializers
from rest_framework.serializers import ListSerializer
from bookmarks.models import Bookmark, Tag, build_tag_string, UserProfile
from bookmarks.services.bookmarks import (
create_bookmark,
update_bookmark,
enhance_with_website_metadata,
)
from bookmarks.services import bookmarks
from bookmarks.services.tags import get_or_create_tag
from bookmarks.services.wayback import generate_fallback_webarchive_url
from bookmarks.utils import app_version
class TagListField(serializers.ListField):
@@ -101,12 +98,20 @@ class BookmarkSerializer(serializers.ModelSerializer):
tag_string = build_tag_string(tag_names)
bookmark = Bookmark(**validated_data)
saved_bookmark = create_bookmark(bookmark, tag_string, self.context["user"])
disable_scraping = self.context.get("disable_scraping", False)
disable_html_snapshot = self.context.get("disable_html_snapshot", False)
saved_bookmark = bookmarks.create_bookmark(
bookmark,
tag_string,
self.context["user"],
disable_html_snapshot=disable_html_snapshot,
)
# Unless scraping is explicitly disabled, enhance bookmark with website
# metadata to preserve backwards compatibility with clients that expect
# title and description to be populated automatically when left empty
if not self.context.get("disable_scraping", False):
enhance_with_website_metadata(saved_bookmark)
if not disable_scraping:
bookmarks.enhance_with_website_metadata(saved_bookmark)
return saved_bookmark
def update(self, instance: Bookmark, validated_data):
@@ -117,7 +122,7 @@ class BookmarkSerializer(serializers.ModelSerializer):
if not field.read_only and field_name in validated_data:
setattr(instance, field_name, validated_data[field_name])
return update_bookmark(instance, tag_string, self.context["user"])
return bookmarks.update_bookmark(instance, tag_string, self.context["user"])
def validate(self, attrs):
# When creating a bookmark, the service logic prevents duplicate URLs by
@@ -163,4 +168,11 @@ class UserProfileSerializer(serializers.ModelSerializer):
"display_url",
"permanent_notes",
"search_preferences",
"version",
]
read_only_fields = ["version"]
version = serializers.SerializerMethodField()
def get_version(self, obj: UserProfile):
return app_version